1984年版《计算机程序的构造和解释》的原序
教育家、将军、减肥专家、心理学家和父母做规划(program),而军队、学生和另一些社会阶层则被规划(are programmed)。解决大规模问题需要做好一系列规划,其中大部分东西只能在工作过程中做好。在这些规划里,充斥着与手头问题的特殊性相关的情况。而要想把做规划这件事本身作为一种智力活动来欣赏,就必须转到计算机程序设计(programming),你需要读或写计算机程序——而且要大量地做。这些程序具体是关于什么、服务于哪一类应用等的情况常常不太重要,重要的是它们的性能如何,在用于构造更大的程序时能否与其他程序平滑衔接。程序员必须同时追求具体部分的完美和汇合的适宜性。在这本书里使用“程序设计”一词时,我们关注的是程序的创建、执行和研究,这些程序用一种Lisp方言书写,为了在数字计算机上执行。采用Lisp不会对我们可以做程序设计的范围强加任何约束或限制,只不过是确定了程序描述的记法形式。
本书将要讨论的问题都要求我们聚焦于三类现象:人的大脑,计算机程序的集合,以及计算机本身。每个计算机程序都是现实的或者精神中的某个过程的一个模型,通过人的头脑孵化出来。这些过程出自人们的经验或者思维,数不胜数,细节繁杂而琐碎,任何时候都只被部分地理解。通过程序模拟相应过程,几乎不可能做到永远令人满意的程度。正因为这些,即使我们写出的程序是一堆经过仔细雕琢的离散符号,是交织互联的一组函数,它们也需要不断演化:当我们对模型的认识更深入、更扩大、更广泛时,就需要去修改程序,直至这一模型最终到达一种亚稳定状态。而在这时,程序中就又会出现另一个需要我们去为之奋斗的模型。计算机程序设计领域之令人兴奋的源泉,就在于它所引起的连绵不绝的发现,在我们的头脑中,在由程序表达的计算机制中,以及在由此所推动的认知爆炸中。如果艺术解释了我们的梦想,那么计算机就是以程序的名义执行着它们。
就本身的所有能力而言,计算机就是一位一丝不苟的工匠:它的程序必须正确,我们希望说的所有东西,都必须表述得准确到每一点细节。就像在其他所有符号活动中一样,我们需要通过论证使自己相信程序的真。我们可以为Lisp本身赋予一个语义(可以说是另一个模型)。假如说,一个程序的功能可以在(例如)谓词演算里精确描述,那么就可以用逻辑方法做出一个可接受的正确性论证。不幸的是,随着程序变得更大、更复杂(实际上它们几乎总是如此),这种描述本身的适宜性、一致性和正确性也都会变得更令人怀疑。因此,很少能看到有关大型程序正确性的完全形式化的论证。因为大程序是从小东西成长起来的,所以,开发出标准化的程序结构的“武器库”,并确认其中每种物件的正确性——我们称这些为惯用法,再学会如何去利用一些已经证明很有价值的组织技术,把这些结构组合成更大的结构,这些都是至关重要的。本书将详细地讨论这些技术。理解它们,对参与这种被称为程序设计的富于创造性的事业是最本质的。特别值得说明的是,发现并掌握强有力的组织技术,能大大提升我们构造大型重要程序的能力。反过来,写大规模的程序非常耗时费力,这种情况也推动我们去发明新方法,减轻由于大程序的功能和细节而引起的沉重负担。
与程序不同,计算机必须遵守物理定律。如果要快速执行——几纳秒完成一次状态变换——就必须在很短的距离(至多1 ½ ft[1])内传导电子,还需要消除由于在小空间里集聚了大量元件而产生的热量。人们已经开发了一些精致的工程艺术,能够在功能多样性与元件密度之间取得平衡。在任何情况下,硬件都在比我们编程时需要关心的层次更基础的层次上操作。把我们的Lisp程序变换到“机器”程序的过程本身,也是通过程序设计做出的抽象模型。研究和构造这类程序,能使人更深刻地理解与构造抽象模型的程序设计有关的程序组织问题。当然,计算机本身也可以这样模拟。请想一想:最小的物理开关元件在量子力学里建模,量子力学由一组微分方程描述,这些方程的细节行为可以通过数值近似来把握,这种数值用计算机程序描述,而计算机程序的组成……!
区分上述三类需要关注的事物,不仅是为了策略上的便利。即使有人说这些区分不过是在人的头脑里,这种逻辑区分也会带来这些关注点之间符号的加速流动,其在人们经验中的丰富性、活力和潜力,只能由生活自身的演进去超越。我们至多能说,这些关注点之间的关系是元稳定的。计算机从来都不够大也不够快。硬件技术的每次突破都带来了更大规模的程序设计事业,一些新的组织原理,以及抽象模型的丰收。每个读者都应该反复自问“往哪里去?往哪里去?”——但不要问得过于频繁,以免你忽略了程序设计的乐趣,把自己禁闭到一种自寻烦恼的哲学中。
在我们写出的程序里,有些就是计算一个精确的数学函数(但是绝不够精确),例如排序,或者找出一系列数中的最大元,或者确定素数性,或者找出平方根。我们把这种程序称为算法,人们对它们的最佳行为已经有了许多认识,特别是关于两个重要参数:执行的时间和对数据存储的需求。程序员应该追求好的算法和惯用法。即使某些程序难以精确描述,程序员也有责任去估计它们的性能,并且要继续设法改进之。
Lisp是幸存者,已经被使用了四分之一世纪。在现存的程序设计语言里,只有Fortran比它的寿命更长些。这两种语言都支持一些重要领域中的程序设计需求,Fortran用于科学与工程计算,Lisp用于人工智能。这两个领域现在仍然很重要,它们的程序员都如此倾心于这两种语言,因此,Lisp和Fortran都还可能继续生存至少四分之一世纪。
Lisp一直在改变。本教科书使用的Scheme方言就是从原本的Lisp演化出来的,并在若干重要方面与之相异,包括变量约束的静态作用域,以及允许函数生成函数作为值。在语义结构上,Scheme更接近Algol 60而不是早期的Lisp。Algol 60不可能再变成活语言了,但它还活在Scheme和Pascal的基因里。很难找到这样两种语言,它们如此清晰地代表着围绕这两种语言聚集起来的两种差异巨大的文化。Pascal是为了建造金字塔——宏大壮观、激动人心,是由各就其位的巨石筑起的静态结构。而Lisp则是为了构造有机体——同样壮观且激动人心,是由各就其位但却永不静止的无数简单有机体片段构成的动态结构。这两种语言采用了同样的组织原则,除了特别重要的一点不同:托付给Lisp程序员个人的对所提供功能的自由支配权,远远超过在Pascal领域可以看到的东西。Lisp程序极大地提升了函数库的地位,使其可用性超越了催生它们的具体应用。Lisp的内置数据结构——表——对这种可用性提升起着最重要的作用。表的简单结构和自然可用性反射回函数,使它们具有了奇异的普适性。而在Pascal里,数据结构的过度声明带来函数的特异性,阻碍并惩罚临时性的合作。用100个函数在一种数据结构上操作,优于用10个函数在10种数据结构上操作。作为这些的必然后果,金字塔矗立在那里千年不变,而有机体则必须演化,否则就会消亡。
为了看清这种差异,请将本书中展示的材料和练习与任何采用Pascal的第一门课程的教科书中的材料做个比较。请不要费力地去想象,说这不过是一本MIT采用的教科书,其特异性仅仅是因为它出自那个地方。准确地说,任何一本严肃的关于Lisp程序设计的书都应该如此,无论其学生是谁,在什么地方使用。
请注意,这是一本关于程序设计的教科书,它不像大部分有关Lisp的书,因为那些书多半是为帮助人们在人工智能领域工作做好准备。当然,无论如何,随着研究中系统规模的不断增长,软件工程和人工智能关心的重要程序设计问题正趋于融合。这也解释了为什么在人工智能领域之外的人们对Lisp的兴趣在不断增加。
根据人工智能的目标可以预见,有关的研究将产生许多重要的程序设计问题。在其他程序设计文化中,问题的洪水孵化出一种又一种新语言。确实,在任何极大规模的程序设计工作中,为了控制和隔离作业模块之间的信息流动,一条有用的组织原则就是发明新语言。当我们逐渐逼近系统的边界,在那里人们需要最频繁的交互,相关的语言也倾向于越来越不基础。作为结果,系统里包含大量重复的复杂语言处理功能。Lisp有如此简单的语法和语义,程序的语法分析可以看作很简单的工作。这样,语法分析技术对Lisp程序几乎没价值,语言处理器的构造从来都不是大型Lisp系统的成长和变化速度的障碍。最后,正是这种语法和语义的极端简单性,导致了Lisp程序员的所有负担和自由。任何规模的Lisp程序,除了只有几行的小程序,都饱含着根据情况精心设计的各种函数。发明并调整,调配恰当之后再去发明!让我们举起杯,祝福那些把他们的思想镶嵌在重重括号之间的Lisp程序员。
Alan J.Perlis
康涅狄格州,纽黑文
[1]1ft=30.48cm。——编辑注