Pascal 发展回忆录

Pascal
创立于 1970 年,经过漫长的发展,它现在已经成为使用最广泛的编程语言之一。本文总结了 Pascal 诞生的原因,深入讨论了该语言的优缺点以及它的发展历程。它的影响主要源于它是结构化编程的工具,是编程语言进一步发展的基石,也是程序验证实验的基础。

早期历史

Pascal 是在 1968/69 年开始设计的,以一位法国哲学家、数学家的名字命名,他在 1642 年设计了首批真正意义上的数字计算器的小工具。Pascal 的第一个编译器于 1970 年初投入使用,当时,该语言的定义早已发布。这些事件构成了 Pascal 的历史节点。但是,Pascal 真正的开始可以追溯到更远的时候。揭示 Pascal 诞生前的事件和发展状况也是一件有趣的事情,因为在这个过程中,我还会阐述其获得广泛使用的过程。

在 1960 年代初期,存在两种主要的科学语言: Fortran
Algol 60
。前者已被广泛使用并得到大型计算机制造商的支持;后者是由国际计算机专家委员会设计的,虽然缺乏大公司的支持,但是其系统架构统一,语言定义简洁规整。显然,Algol 值得拥有更多关注和更广阔的应用领域。为了实现这一目标,Algol 需要重新构造,以便使其适合于数值计算以外的其他用途。为此,IFIP 建立了一个工作组,在章程中制定了 Algol 的发展目标。有人希望它可以弥合科学和商业程序之间的鸿沟,到 1960 年代中期,它已经成为 Fortran 和 Cobol 的缩影。我有幸加入了 2.1 工作组。在 1964 年,关于语言设计的一般哲学、形式定义方法、语法细节、字符集以及与编程相关的主题的讨论变得非常广泛,这表明人们对所采用的方案缺乏共识。但是,讨论过程中提出的丰富思想及经验总结也为将其合并为有效的方案形成了支撑。
随着讨论会议数量的增加,工作组的成员主要分成了两个派系。一部分人员雄心勃勃,不愿在 Algol 60 框架的基础上发展,他们不怕重新按照还停留在理论阶段上的构想来不计结果的实施功能创新,并渴望另起炉灶建立一个与 Algol 60 设定的里程碑类似的里程碑。而另一部分人则更加务实。他们希望保留 Algol 60 的主体,并对其进行一定的功能扩展,以提高其衍生语言的的应用范围,但仍保留其本身的有序结构。基于这个原则,他们提出了为双精度实数和复数添加基本数据类型的建议,另外他们还希望改进 COBOL 已知的记录结构,将 Algol 的按名称调用替换为按引用调用参数,并在有限的迭代中以更有效的版本代替 Algol 过于笼统的陈述。他们将未经验证的新颖想法并没有很大把握地纳入官方语言版本,但是假如他们不这样做,项目的里程碑事件也不过就变成了一个摆设。
1965 年,我受命向工作组提交一份提案,该提案主要支持了务实主义的观点。然而,在同年 10 月的一次会议上,大多数成员投票赞成了 Algol 60 小组前成员 A. van Wijngaarden 提出的竞争性提案,并在 1966 年秋天的一次会议上决定选择该提案作为 Algol X 的指导提案。不幸的是,正如大多数人所预期的那样,Algol 68 的复杂性导致项目进度迟缓,其结果是在 70 年代初开始实施时,Algol 60 的许多用户转而采用了其他语言。
尽管我的提案没有获得大家的认可,但我还是继续按照自己的方案执行,并结合吸收了 CAR.Hoare 建议的动态数据结构和指针绑定的概念。值得高兴的是,斯坦福大学针在新的 IBM 360 计算机上使用了这个方案,该项目也得到了美国国家科学基金会的资助,最终该项目被命名为 Algol W。该系统已被许多大学采用来教授编程课程,但是该语言仍然局限于 IBM 360/370 计算机。本质上,Algol W 扩展了 Algol 60,使其具有代表双精度浮点数和复数的新数据类型、位字符串以及通过指针链接的动态数据结构。尽管采用了务实的预防措施,但事实证明这种实现方案也很复杂,需要运行时支持包。因此它很难成为在系统编程方面受欢迎的工具,部分原因在于它承担了一些系统编程任务不必要的功能,另外还因为它缺乏足够灵活的数据结构工具。因此,我决定追寻我最初的目标,即设计通用语言,我并不觉得照顾二十几位专家的意见并达成共识是很好的方案,我也不愿意这样做。
过去的经历让我对项目委员会的能力产生了怀疑,许多委员会成员仅仅参与辩论和决策,但很少有人参与实践这些困难的实际工作。1968 年,我担任苏黎世联邦理工学院(ETH)的教授一职,在那里,Algol 60 成为了数值计算研究人员的首选语言。但 1965 年(可能在 1970 年),学院对 CDC 计算机的收购使大家再也不那么喜欢 Algol 60 了,因为用于这些计算机的 Algol 编译器设计较差,无法与 Fortran 同类产品竞争。此外,鉴于要在 Fortran 和汇编器代码两工具之间做出唯一的选择,程序设计(尤其是系统程序设计)的课程讲授也似乎变得不再有吸引力。毕竟,结构化编程的优点不再具有很大的竞争力,大家还需求一种方便的编译器来支持该语言在实践中的运用。

E. W. Dijkstra 概述了结构化编程的原则,在对抗后来被称为软件危机的战斗中向前迈出的重要一步。有人认为应该在编程入门课程之后立刻教授该学科,而不是当大家都有一定经验之后再学习这些原则,这种见解在今天仍然获得大家的赞同。结构化程序设计和逐步求精法标志着程序设计理论的开端,并使程序设计成为学科能力发展的基石。因此,一种新语言的定义及其编译器的开发不仅仅是语言设计方面的研究项目,而且是被大众接受非常有必要的前提。在接下来的几十年中,这种情况还会再次发生,当时官方给出的最好建议是:假如缺少足够的工具,那请您自行构建!
在 1968 年,项目的目标有两个:使该语言适合于以简洁和合乎逻辑的方式表达当时已知的基本结构,并且其实现要高效,且与现有的 Fortran 编译器竞争。考虑到 Fortran 的计算机(CDC 6000)的设计,后一项要求相当苛刻。特别是在动态数组和递归过程方面面临着巨大的障碍,因此这两项都被排除在该语言的初稿之外。禁止递归是一个错误,并且很快得到了纠正,因为我们慢慢意识到,认为短暂的工具不足会严重影响语言的发展是错误的判断。编写编译器的任务是在 1969 年分配给一个研究生(E. Marmier)的。由于他的编程经验仅限于 Fortran,因此该编译器主要用 Fortran 开发,之后将其翻译为 Pascal 并进行后续的自动编译。完成后,事实证明,这是另一个严重的错误。
Fortran 不足以表达编译器的复杂数据结构,这导致程序问题不断,其翻译也基本要重新设计。因为问题固有的结构在 Fortran 公式中不可见。编译器依赖于基于 Algol-W 编译器采用的表驱动自底向上(LR)方案的语法分析。1960 年代,语法分析非常复杂,主要是因为处理高级语言需要很强的功能和灵活性。于是我想到,如果在考虑语言分析过程的情况下选择一种语言的语法,就可以使用一种更简单、更直观的方法。因此,构建编译器的第二次尝试是从源语言本身的形式开始的,这个时候语言已演变为 1970 年出现的 Pascal。该编译器是基于经过验证的自上而下、递归下降原则进行语法分析的单遍系统。到这里我们已经注意到这种方法是有效的,因为取消了对递归的禁令:程序的递归性是正常情况。实施者团队由 U. Ammann、E.Marmier 和 R. Schild 组成。程序完成后——使用新语言进行完整的编程体验—— Schild 被安排在家里呆两个星期,这是他将程序翻译成 CDC 计算机上可用的辅助低级语言所花费的时间。此后,对语言的真正导入过程也开始了。该编译器于 1970 年中完成,并在此时发布了语言定义。除了 1972 年的一些细微修改外,此后一直保持稳定。
我们从 1971 年末开始在入门编程课程中使用 Pascal。由于 ETH 直到十年后才提供计算机科学程序课程,因此使用 Pascal 向工程师和物理学家教授编程的做法引起了一定的争议。我始终认为,在学校中使用工业生产中的教授方法,或在工业生产中使用学校的教学方法,会构成阻碍进步的恶性循环。但这可能是我顽固的执念,而不是任何使 Pascal 继续使用的合理论据。十年后,没有人介意。为了协助教学,凯西·詹森(Kathy Jensen)开始编写教程文本,通过许多示例解释了 Pascal 的主要编程概念。该文本首先作为技术报告印刷,随后出现在 Springer-Verlag 的演讲笔记系列中。

语言介绍

语言设计师的主要职责是头脑清晰地收集功能或概念相关的提议。一旦选择了这些概念,就必须找到表达它们的形式,即必须定义语法。表达个人观念的形式必须精心塑造成一个整体,这是最重要的,否则该语言将显得不连贯,就像是将各个结构移植到其上的骨架一样,让人觉得这是事后的补丁。经过足够的时间验证,Algol 的主要缺陷已为人所知并逐步消除。例如,避免歧义的重要功能。在许多情况下,必须决定是否以独立的方式解决问题,还是保持与 Algol 的兼容性。这些方案选择有时是互斥的。回想起来,支持兼容性的决定很不明智,因为这会使过去的不足之处仍然存在。兼容性的重要性被我们高估了,就像 Algol-60 社区的重要性和规模一样。这种情况的例子有很多,比如:没有结束符号的结构化语句的句法形式、指定函数过程的结果的方式(分配给函数标识符)以及形式过程参数的不完整说明。所有这些缺陷后来在 Pascal 的后继语言 Modula-2 中得到了纠正。例如, 保留了 Algol 的二义性条件语句。


复制代码

IFpTHENIFqTHENAELSEB

根据指定的语法,可以通过以下两种方式对其进行解释:


复制代码

IFpTHEN[IFqTHENAELSEB]
IFpTHEN[IFqTHENA]ELSEB

Pascal 保留了这种语法上的歧义,但是选择了以下解释方式:每个 ELSE 都与左侧最接近的 THEN 相关联。对其如何改进大家是知道的,但在当时没有采纳使用,包括为每个结构化语句提供一个明确的结束符号,这导致两种截然不同的形式,如下所示:


复制代码

IFpTHENIFqTHENAELSEBENDEND
IFpTHEN
IFqTHENAEND
ELSEB

END

Pascal 还保留了一个正式过程参数类型的不完全规范,为违反类型检查留下了一个危险的漏洞。


复制代码

PROCEDUREP(PROCEDUREq);

BEGINq(x,y)END;
PROCEDUREQ(x: REAL);

BEGIN…END;

之后调用 P(Q)。这个 q 是用错误数量的参数调用的,这些参数通常在编译时无法检测到。 与这种对传统的让步形成对比的是条件式的消除。因此,IF 符号明显地成为了一个陈述的开始和使人困惑的结构标志。


复制代码

IFpTHENx:=IFqTHENyELSEzELSEw

Algol 的巴洛克式陈述已被更高效的改进版本替代,从而将控制变量限制为简单变量,并且限制仅评估一次,而不是在每次重复之前进行评估。对于更常见的重复情况,引入了 while 语句。因此,就不可能形成诸如误导性、非终止性的陈述,比如:


复制代码

FORi:=0STEP1UNTILiDOS

也不会有下面这样相当晦涩的表述:


复制代码

FORi :=n-1, i-1WHILEi > 0DOS

可以更清楚地表达为:


复制代码

i :=n;
WHILEi >0DOBEGINi := i-1; SEND

Pascal 的主要创新是合并各种数据类型和数据结构,类似于 Algol 引入的各种语句结构。Algol 仅提供三种基本数据类型,即整数、实数和真值以及数组结构。Pascal 引入了其基本类型,并提供了定义新基本类型(枚举,子范围)以及结构化新形式的可能性:记录、集合和文件(顺序),其中一些已出现在 COBOL 中,当然最重要的是结构定义的递归性,以及随之而来的自由组合和嵌套结构的可能性。
除了程序员定义的数据类型外,类型定义和变量声明之间也有明显的区别,变量是类型的实例。Algol 中已经存在的强类型化概念成为安全编程的重要催化剂。类型应理解为变量的模板,用于指定在变量存在的时间跨度内保持固定的所有属性。尽管其值发生变化(通过分配),但其可能值的范围以及结构仍保持固定。静态属性的这种明确性使编译器可以验证是否遵守管理类型的规则。将属性与程序文本中的变量的绑定称为早期绑定,这是高级语言的标志,因为它可以清楚地表达程序员的意图,而不受程序执行动态的影响。但是,严格遵守(静态)类型的概念导致了一些不太理想的后果。我们在这里指的是缺少动态数组。这些可以理解为以元素数量(或边界索引值)为参数的静态数组。Pascal 不包括参数化类型,主要是因为实现原因,尽管该概念已广为人知,尽管缺少动态数组变量可能不是很严重,但如果没有编译器设计人员的参与,那么从数字算法程序员的角度来看,缺少动态数组参数显然会被视为缺陷。例如,以下声明不允许使用 x 作为其实际参数调用过程 P:


复制代码

TYPEA0 =ARRAY[1..100]OFREAL; A1 =ARRAY[0..999]OFREAL;
VARx: A1;
PROCEDUREP(x: AO);BEGIN…END

Pascal 的另一个重要贡献是对结构和访问方法的概念进行了清晰的概念和外延上的分离。在 Algol W 数组中只能将其声明为静态变量,因此只能直接访问,而记录结构化变量只能通过引用(指针)进行访问,即间接访问。在 Pascal 中,所有结构都可以直接访问或通过指针访问,而间接访问由显式的解引用操作符指定。 这种关注的分离被称为“正交设计”,并且在 Algol 68 中就使用了(也许是极端的)。显式指针(即指针类型的变量)的引入是应用范围显著扩大的关键。使用指针,可以建立动态数据结构,就像列表处理语言一样。值得注意的是,在不牺牲严格的静态类型检查的情况下,实现了数据结构的灵活性。这是由于指针绑定的概念。如所建议的那样,将每个指针类型声明为绑定到所引用对象的类型。例如,如下声明:


复制代码

TYPEPt = ^Rec;Rec= RECORD x,y:REALEND; VAR p, q: Pt;

然后,只要已正确初始化 p 和 q,就可以保证它们都保存引用 Rec 类型的记录的值,也可以保存常量 NIL 形式为 p ^ .x:= py + q ^ .x 的语句与简单的 x:= x + y 一样具有类型安全性。
实际上,在所有应用程序中,除了数值计算之外,指针和动态结构比动态数组要重要得多。复杂地连接到指针的是存储分配机制。由于 Pascal 适合作为系统构建语言,因此它尝试不依赖 Algol W 所必需的内置运行时垃圾收集机制。采用的解决方案是提供一个内部过程 NEW 来分配 a 变量位于称为堆的存储区域中,并且是用于释放的补充变量(DISPOSE)。NEW 实施起来很简单,对 DISPOSE 倒是可以忽略,实际上这样做是明智的,因为依赖于程序员信息的系统过程本质上是不安全的。鉴于其复杂性,未考虑提供垃圾收集功能的方案。毕竟,局部变量以及程序员定义的数据类型和结构的存在需要非常复杂的方案,而这取决于系统的完整性。收集器必须能够依赖有关所有变量及其类型的信息。该信息必须由编译器生成,此外,在程序执行期间必须不可能使该信息无效。
在寻找 Algol 60 的后继产品的时代,参数传递方法的主题已经引起了无休止的争论和麻烦。已经清楚地确定了其名称参数的不切实际性,并且普遍接受了值参数的不可缺少性。然而,对于引用参数,尤其是对于结构化操作数,存在有效的参数。另一方面,对于结果参数也有充分的理由。在前一种情况下,形式参数构成指向实际变量的隐藏指针;在后一种情况下,形式参数是在过程终止时分配给实际变量的局部变量。选择参考参数(在 Pascal 中称为 VAR 参数)作为 value 参数的唯一替代方法被证明是简单、恰当和有效的。
最后很关键的一点是,Pascal 包含了关于输入和输出的声明,Algol 在这方面的弊病一直是其不断遭受批评的根源。 特别是出于 Pascal 作为一种教学语言的考虑,我们选择了这种语句的一种简单形式。它们的第一个参数指定一个文件,如果省略,将导致从默认介质(例如键盘和打印机)读取数据或将数据写入其中。为此,在语言定义中包含特殊说明而不是假定特殊的标准过程的原因是希望允许使用可变数量和不同类型的参数。


复制代码

Read(x.y);… ;Write(x,y,z)

如前所述,语言设计人员会根据自己的经验、文献或其他语言来收集常用的编程结构,并将它们塑造成句法形式,以使它们共同形成一种集成的语言。Pascal 的基本框架源自 Algol W,而 CAR.Hoare 的建议则帮助带来了许多新功能,包括枚举、子范围、集合和文件类型。类似于 COBOL 的记录类型的形式也应归功于 Hoare,以及通过众所周知的抽象来表示计算机的“逻辑词”的想法,即集合(小整数)。这些“细枝末节”经常在 IFIP 2.1 工作组会议(Algol)上进行介绍和讨论,然后作为通讯发布在 Algol 公告中。它们被收集在他的《数据结构注释》中(Hoare,1972)。在 Pascal 中,它们被提炼成语法和语义一致的框架,从而使结构可以自由组合。Pascal 允许定义记录数组、数组记录、集合数组和带有文件的记录数组的定义,这里仅举这几例。当然,由于有限的资源,实现必须对嵌套深度施加一定的限制,并且某些组合(例如文件文件)可能根本不被接受。这种情况可以作为区分该语言定义的一般概念和管理特定实现的补充性限制性规则的示例。
尽管丰富的数据类型和结构形式是 Pascal 的主要特征,但并非所有组件都同样被大家接受。我们需谨记,成功是一种主观的素质,不同的观点可能会有很大差异,因此历史会给出合理明确的结论。最激烈的批评来自哈伯曼(Habermann),他指出,Pascal 不是语言设计。除了对类型和结构被合并为一个概念,缺乏条件表达式,乘幂运算符和局部块(这些都存在于 Algol 60 中)之类的构造提出质疑外,他还批评 Pascal 保留了被诟病的对象陈述。事后看来,我们当时应该接受他的意见。当时这个缺陷让很多人放弃了尝试使用 Pascal。
十年后,Pascal 的继任者 Modula-2 有了大胆的提议,提出了一种简化语言的建议,该方法纠正了许多缺点并消除了对 Algol 60 的一些兼容性让步,特别是在语法方面。哈贝曼对批评的正确判断是由勒卡姆(Lecarme)撰写的,他根据他在 Pascal 的教学和编译器设计方面的经验来判断优缺点(Lecarme,1975)。另一个重要的评论讨论了数据类型的结构性与名称对等性的问题,不幸的是,在 Pascal 的定义中,这一区别尚待解决。在标准委员会解决之前,它引起了许多争论。唯一不恰当的构造可能是变体记录。提供它是为了构建不均匀的数据结构。对于数组和动态结构,在 Pascal 中,元素类型都必须通过类型声明进行固定。变量记录允许元素类型的变化。Pascal 变体记录的不当之处在于,它提供了比实现此合理目标所需的更大的灵活性。在动态结构中,通常每个元素都保持其创建所定义的相同类型。但是,变体记录不仅允许构造异类结构,即具有不同,但相关类型元素的结构。它允许元素本身的类型随时更改。这种额外的灵活性具有令人难以接受的毛病,它要求在运行时对此类变量或其组件之一的每次访问进行类型检查。Pascal 的大多数实现者认为,这种检查将过于昂贵,从而增加了代码并降低了程序效率。结果,变体记录成为所有喜欢技巧的程序员打破类型系统的最喜欢的功能,而这些技巧通常会变成陷阱和灾难。各种记录也成为程序可移植性概念的主要障碍。例如,声明:


复制代码

VAR R:RECORDmaxspeed:INTEGER;CASEv. VehicleOFtruck: (nofwheels:INTEGER); vessel: (homeport:String)END

在此,仅当 R.v 的值为 truck 时,才适用标记 R.nofwheels;仅当 R.v = vessel 时,才使用 R.homeport。没有编译器检查可以防止错误使用指定符,这在分配的情况下可能是灾难性的,因为实现将其变体功能用于通过覆盖字段 nofwheels 和 homeport 来节省存储空间。关于输入和输出操作,Pascal 分离了数据传输(去往或来自外部介质)和表示转换(二进制到十进制,反之亦然)的概念。在外部,媒体信息应表示为字符文件(序列)。 表示转换是通过特殊的读和写语句来表示的,这些语句具有过程的外观,但允许参数的数量是可变的。后者实质上是习惯于 Fortran I / O 语句的程序员的妥协结果,而序列作为结构形式的概念是基本的。也许在这种情况下,提供任何(甚至程序员定义的)元素类型的序列也比实际中真正需要的更多。结果是,与所有其他数据类型相比,文件需要内置的运行时例程提供一定程度的支持,而这些机制在程序文本中未明确显示。Pascal -Modula-2 和 Oberon 的继任者——后来从文件的概念中撤出了与数组和记录相同级别的结构形式,才使这成为可能。因为可以通过模块(库软件包)提供排序机制实现。但是,在 Pascal 中,模块的概念尚不存在。Pascal 程序被视为单一的整体文本。对于练习相当紧凑的教学目的,这种观点可能是可以接受的,但这对于大型系统的建设是站不住脚的。然而,令人欣喜的是,Pascal 编译器可以写成单个 Pascal 程序。

未来的发展

虽然 Pascal 在教学方面似乎满足了我们的期望,但编译器在两个方面的效率仍然没有达到规定的目标: 首先,编译器作为一个相对较大的独立程序,给学生带来了相当长的“周转时间”。为了缓解该问题,我设计了一部分语言,其中包含我们认为入门课程中涉及的那些功能,以及一个适合 16K 字存储区的编译器 / 解释器程序包,该功能属于计算中心最受欢迎的程序。Pascal-S 软件包已发布在一份报告中,它是以源代码形式提供的广泛可用的早期综合系统之一。 相比之下,它比 Fortran 程序仍然快得多,这是 Pascal 的对手们无法否认的事实。
我们认为结构化程序设计支持结构化的语言和编译生成代码的效率不一定是相互排斥的,由此我们启动了第三个编译器项目。这一方面是为了证明结构化自顶向下设计的优势和步进式细化 (阿曼,1974),另一方面是要着重生成高质量的代码。该编译器由 U. Ammann 编写,1976 年完成,非常出色地实现了这两个目标。尽管结果是高质量和可靠性的复杂编译器,但事后看来,我们必须坦白承认我们投入的精力与其效果不相称,结果并没有赢得许多工程师甚至少数物理学家的支持。Fortran 程序“运行速度更快”的论点被“我们的程序用 Fortran 编写”所代替。是什么让我们有能力向十年以上的专家教授结构化的“更好”的编程技术?而且,代码是为 CDC 6000 计算机生成的,该计算机具有 60 位字节和超级 RISC 结构,因此根本不适合该任务。安曼(Ammann)的大部分工作都用于实现记录的属性。尽管从语义上讲是无关紧要的,但出于存储经济性的考虑,才做出了这样的要求。拥有设计语言和程序的自由,将大大简化该项目。
不过 Pascal 的进一步发展却来自另一个事件。在 Pascal 定义发布后不久,我们收到了一封信函,信中表明对这种语言有兴趣,并要求主要在非 CDC 计算机用户的人群中进行编译器构造方面的帮助。正是这种刺激促使我设计了更优化的计算机体系结构。Ammann 编译器的一个版本(可以很容易地从优化序列的早期阶段派生而来)将为该“理想”架构生成代码,该代码以代表解释器的 Pascal 程序的形式进行描述。1973 年,该体系结构被称为 P-machine,代码被称为 P-code,编译器被称为 P-compiler。P 工具包由 P 代码的编译器和作为 Pascal 源程序的解释器组成 [Nori,1981]。使用者可以将他们的工作限制为以自己喜欢的汇编代码对解释器进行编码,或者着手修改 P 编译器的源代码并替换其代码生成例程。
事实证明,这种 P 系统是 Pascal 推广到许多计算机的关键,但是许多人不愿使用解释性方案,这也导致 Pascal 被归类为“慢语言,仅限于教学中使用”。大约在 1975 年左右,加利福尼亚大学圣地亚哥分校(UCSD)的 K. Bowles 团队也是 P-kit 的使用者。他很有远见,看到用于解释系统的 Pascal 编译器很可能适合于一台微型计算机,他鼓起勇气尝试。 此外,P-code 的思想使得在整个 micros 家族上移植 Pascal 变得很容易,并为所有这些产品提供了通用的教学基础。微型计算机刚刚开始出现,它们使用了诸如 Intel 的 8080、DEC 的 LSI-11 和 Rockwell 的 6502 之类的早期微处理器。在欧洲,当时他们还鲜为人知。Bowles 不仅移植了我们的编译器。他的团队还围绕编译器构建了一个完整的系统,包括程序编辑器、文件系统和调试器,这与其他教育系统相比,大大减少了编辑 – 编译 – 测试步骤所需的时间。从 1978 年开始,这种 UCSD-Pascal 系统迅速将 Pascal 传播给了越来越多的用户。
与过去十年在“大型机”上使用的系统相比,它在一年内赢得了“Pascal 朋友”更多的支持。这一惊人的成功来自三个方面:(1)在高级计算机上可以使用一种高级语言来普及教育机构;(2)Pascal 由集成系统而不是“独立”编译器支持;(3)最重要的是,Pascal 被提供给了许多计算机新手,即那些没有以前编程习惯负担的人。 为了采用 Pascal,他们不必放弃先前在学习汇编语言或 Fortran 编码的所有特性上的大量投资。微型计算机使编程成为一项公共活动,而且 Pascal 在微型计算机上有效地击败了 Fortran。到 1978 年,从 Intel 8080 微处理器到 Cray-1 超级计算机,主机上存在 80 多种不同的 Pascal 实现。但是 Pascal 的用处不仅仅限于教育机构。到 1980 年,所有四个主要工作站制造商(Three Rivers、HP、Apollo 和 Tektronix)都使用 Pascal 进行系统编程。 除了作为 Pascal 实现传播的主要媒介之外,P 系统在证明编译器和系统程序的可理解性、可移植性和可靠性方面具有重要意义。许多程序员从 P 系统中学到了很多东西, 包括那些不把工作建立在 P-system 基础上的实现者,还有一些以前从来没有详细研究过编译器的人。 由于有源代码形式的编译器,P-system 成为课外教育的一个有影响力的工具。
几年前,有项目曾尝试将 Pascal 编译器传输到其他主机上。在这些项目中,不使用任何解释器或中间代码。相反,他们需要设计本机代码的新生成器。这些项目中的第一个也是最成功的一个是由贝尔法斯特女王大学的 J. Welsh 和 C. Quinn 负责的 ICL 1900 计算机项目。该项目之所以值得特别提及,因为它应被视为最早,真正成功的软件工程企业之一。 由于贝尔法斯特没有 CDC 的计算机,所以我们的目标是采用一种方法,尽可能少地使用 CDC 中心的计算机。剩下的不可避免的事情将在苏黎世联邦理工学院的短暂访问中进行。Welsh 和 Quinn 通过替换所有影响代码生成的语句修改了用 Pascal 编写的 CDC-Pascal 编译器。此外,他们编写了 ICL 体系结构的加载程序和解释器,从而允许在 CDC 计算机上执行一些测试。
所有这些组件都是在关键的访问之前编写的,并且是在没有任何测试可能性的情况下完成的。在苏黎世,程序已编译,一周内纠正了一些小错误。回到贝尔法斯特,生成的编译器代码在更正了单个剩余错误之后可以直接由 ICL 机器执行。取得这项成就的原因在于,他们非常认真地进行了编程和检查,它证实了通过使用 Pascal 这样的高级语言进行编程所获得的优势,该语言提供了完整的静态类型检查。之所以可称为壮举,还因为一周中有半个多星期的时间都花在寻找一种方法,以阅读 Belfast 带来的程序。由于意识到两台机器的字符集和磁带格式(7 轨和 9 轨磁带)不兼容,Welsh 和 Quinn 决定使用打孔卡作为数据载体。然而,遇到的障碍可能同样难以克服。事实证明,使用 CDC 读卡器读取 ICL 机器打出的卡是一件非常棘手的任务。机器不仅使用不同的字符集和不同的编码,而且某些孔的组合也被读者直接解释为记录的结尾。尽管制造商已尽最大努力确保兼容性!
除了这些危险之外,使用者们还没有考虑到瑞士海关官员带来的风险。装满约四千张卡片的两个盒子肯定引起了人们的高度怀疑,特别是因为这些卡片中有空的小隔间,而且这些小隔间被打孔的间隔不规则。然而,在保证无论如何这些有价值的财产都将被出口后, 这两个可能是走私者的人获准继续执行他们的任务。随后进行了其他移植编译器的工作;其中包括格勒诺布尔的 IBM 360 计算机、特温特的 PDP-11 和汉堡的 PDP-10(Grosse-Lindemann,1976)。到 1973 年,Pascal 已开始广为人知,并已用于教室以及较小的软件项目。接受这种要求的必要前提是,除了语言定义外,还需要提供包括教程资料在内的用户手册。凯西·詹森(Kathy Jensen)着手提供教程部分,并于 1973 年由 Springer-Verlag 出版了该手册,该书首先出版于他们的讲义系列中,但由于销售过快都无法及时发行。随之而来的是来自许多国家的作者提供的越来越多的入门级教科书。用户手册本身后来被翻译成许多不同的语言,并成为畅销书。 在明尼苏达大学的计算中心有一群 Pascal 的忠实粉丝。在安迪·米克尔(Andy Mickel)的领导下,Pascal 用户小组(PUG)成立了,其交流工具是《Pascal 通讯》,最初由 GH 里士满(美国科罗拉多州)编辑,后来由麦克尔(Mlckel)编辑。第一期出版于 1974 年 1 月。它充当了 Pascal 的新实现、新经验以及(当然)有关改进和扩展语言的想法的公告板。它最重要的贡献在于跟踪所有新出现的应用。这有助于消费者双方找到适合其计算机的编译器和实现,以协调推动他们的工作。
在苏黎世联邦理工学院,我们决定继续进行其他项目,并中止编译器的分发,明尼苏达州小组准备接管其维护和分发工作。这里的维护是指适应不断变化的操作系统版本,以及 Pascal 标准的制定。大约在 1977 年,还成立了一个委员会来定义标准。在 Pascal 举行的南安普敦会议上,AM Addyman 请求帮助成立英国标准协会(BSI)下的标准委员会。1978 年,行业代表在 K. Bowles 主持的圣地亚哥会议上开会,定义了 Pascal 的许多扩展标准。这加快了在 IEEE 和 ANSI / X3 的领导下成立标准委员会的步伐。1979 年末,在 ISO 内成立了一个工作组,最后将 IEEE 和 ANSI / X3 委员会合并为单一的 Pascal 联合委员会。美国委员会与英国委员会和 ISO 委员会之间发生了重大冲突,特别是在一致性数组参数(动态数组)问题上,这也是原始 Pascal 与 ISO 所采用的 Pascal 之间的主要区别。另一个是对参数过程和功能的完整参数规范的要求。在动态数组问题上的冲突最终导致一方面采用 ANSI 的标准与另一方面采用 BSI 和 ISO 的标准之间的差异。未扩展的标准在 1981 年被 IEEE 采纳,在 1982 年被 ANSI 采用。不同的标准由 BSI 于 1982 年发布,并于 1983 年被 ISO 批准。同时,几家公司已经使用了 Pascal,并添加了自己的,据说是必不可少的扩展。
标准的制定只是将它们带回统一的保护伞下。如果有什么机会可以实现这个梦想的话,那就是迅速宣布原始语言为官方标准,也许还可以增加一些关于疑难问题的官方澄清。 不过这群人中有几个人成了魔鬼诱惑的牺牲品:他们用自己喜欢的功能扩展了语言。我在原始设计中已经考虑了其中的大多数功能,但是由于清晰的定义或有效的实现上的困难,或者由于对程序员的好处存在疑问而放弃了。结果,漫长的辩论开始了,召开了很多会议。当委员会最终提交改进文件时,其方案几乎可以追溯到最初的 Pascal 设计方案上。但是,自该报告发表以来已经过去了十年,在此期间,个人和公司生产并分发了编译器:他们不愿意为了遵守最新标准而修改它们,甚至不愿放弃自己的扩展。该标准的实现随后在 [Welsh,1986] 中发布。但是在该标准发布之前,就已经建立了一套程序验证套件,并在促进各种实现之间的兼容性方面发挥了重要作用。在采用该标准后,它的作用甚至进一步增强了,在美国,它使 Pascal 的联邦信息处理标准成为可能。70 年代初期,在大型项目出现重大失败之后,出现了结构化编程和软件工程等术语。他们象征着希望,而且,人们常常认为它是过去所有麻烦的灵丹妙药。这种趋势进一步引起了大家对 Pascal 的兴趣,毕竟 Pasca 呈现的是清晰的结构,并受到 E.W Dijkstra 关于结构化设计的教义的强烈影响。
同样,在 70 年代,人们也认为,针对程序开发的规范性证明是最终目标。 CAR. Hoare 提出了关于编程标记的公理和推理规则(后来被称为 Hoare-logic)。他和我承担了使用这种逻辑来正式定义 Pascal 语义的任务。但是,我们不得不承认,正式定义中必须省略许多功能(例如指针)。Pascal therafter 至少在两个地方充当了实现程序验证器的工具,分别是斯坦福大学和苏黎世联邦理工学院。E. Marmier 扩大了编译器的功能,以接受在可执行语句之后(或之前)保存的程序变量之间关系的断言(以标记注释的形式)。断言检查器的任务是根据 Hoare-logic 来验证或反驳断言和语句的一致性。他是最早朝着这个目标发展的。尽管它能够为各种合理的简单程序确定正确性,但它的主要贡献是消除了一切都可以自动化的简单思想。
Pascal 在语言设计领域产生了深远的影响。它充当了新思想的催化剂,并成为了尝试新思想的媒介,并以此产生了几种后继语言。第一个是 P. Brinch Hansen 的并发 Pascal,它在顺序语言 Pascal 中嵌入了并发过程和同步原语的概念。具有同样目标,但强调基于并发过程的离散事件系统的模拟,产生了 Pascal-Plus 语言,由贝尔法斯特的 J.Welsh 和 J.Elder 开发。Lampson 等人的一个雄心勃勃项目的结果是使用了相当多的语言,该项目的目标是满足现代大规模软件工程的所有需求。尽管在很多细节和语法上都有所不同,但 Pascal 仍然是 Mesa 这种语言的祖先。它增加了有导入和导出关系(即信息隐藏)的模块这种革命性概念。它的编译器引入了独立的模块或软件包编译概念。这种想法后来在 Modula-2 中被采用,与 Mesa 相反,该语言保留了简单、概念经济和紧凑的原则,这使 Pascal 取得了成功。Pascal 的另一种派生是 Euclid 语言。它的语义定义是基于形式主义的,就像 Algol 60 的语法由形式主义的 BNF 定义的一样。Euclid 小心翼翼地去除了无法正式定义的功能。Pascal 对象是 Pascal 的扩展,结合了面向对象编程的概念,即将数据和运算符绑定在一起的抽象数据类型。最后必须提及的语言是 Ada,它的设计始于 1977 年,并受到 Pascal 的明显影响。但是,它缺乏设计的经济性,没有这种经济性,定义就变得繁琐,实现起来也很不尽如人意。

回顾总结

我应邀对 Pascal 的优缺点、其设计中的错误决定及其未来的前景进行评估。我其实不太愿意做这事,而是希望让读者多多关注我自己的后续设计,即 Modula-2 和 Oberon。如果我将它们分别命名为 Pascal-2 和 Pascal-3,可能不会有人问这些问题,因为这些语言的发展历程是显而易见的。对早期的设计决策提出质疑和辩论也是徒劳的。事后看来,更好的解决方案通常很明显。也许最重要的一点是,尽管存在不确定性,但还是有人做出了决定。基本上,该原则应包括一些众所周知的功能,尤其是实施者所熟知的功能,并排除那些仍未尝试和未实施的准则,这是最成功的统一准则。第二个重要原则是在建立完整的实现后发布语言定义。发表完成的工作总是比计划发表的工作更有价值。尽管 Pascal 没有得到行业、专业团体或政府机构的支持,但已得到广泛使用。取得成功的重要原因是,许多有能力认识到其潜力的人积极地参与了其推广。与良好实现的存在一样关键的是文档的可用性。原始报告的简洁性使许多教师将其扩展为有价值的教科书增强了其吸引力。在 1977 年至 1985 年之间,无数书籍以多种语言出现,有效地促进了 Pascal 成为入门编程课程中使用最广泛的语言。好的课程资料和应用是这种发展必不可少的前提。在撰写本文时,Pascal 仍在教学中大量使用。它似乎与 Fortran 一样遭受命运的挑战,在前进的路上遇到了阻碍,但也有比较乐观的人认为 Pascal 的职责是为继任者铺平道路。

致谢

我衷心感谢众多贡献者的工作,这些贡献在使 Pascal 努力取得成功方面发挥了不可或缺的作用,并因此直接或间接地帮助推进了程序设计的发展。特别感谢 CAR.Hoare 向 Pascal 的设计提供了许多启发性的想法,U. Ammann、E. Marmier 和 R. Schild 为创建有效而强大的编译器付出了艰辛的努力,向 A. Mickel 和他的团队表达深深的敬意,他们的热情和不懈的投入建立的用户组和新闻稿使 Pascal 广为人知,以及让 K. Bowles 认识到我们的 Pascal 编译器也适用于微型计算机并据此进一步做了大量工作。我还要感谢无数教科书的作者,没有他们的介绍性论文,Pascal 不会受到大众如此广泛的接受。