教育家、将军、营养学家、心理学家和父母都在编程。军队、学生和一些社会是被编程的。对大型问题的攻克需要一系列的程序,大部分程序是在过程中产生的。这些程序充满了看似特定于当前问题的各种问题。要理解编程本身作为一种智力活动,你必须转向计算机编程;你必须阅读和编写计算机程序——大量的程序。这些程序是关于什么的、服务于什么应用并不重要。重要的是它们执行得如何,以及在创造更大程序时它们如何顺畅地与其他程序配合。程序员必须追求局部的完美和整体的适当。在本书中,"程序"一词的使用聚焦于创建、执行和研究用 Lisp 方言编写、在数字计算机上执行的程序。使用 Lisp,我们限制的不是我们能编程什么,而只是我们程序描述的记法。
我们与本书主题的交往涉及三个现象焦点:人类心智、计算机程序的集合,以及计算机本身。每一个计算机程序都是一个模型,孕育于心智之中,是对真实或心智过程的模拟。这些过程源于人类经验和思维,数量庞大、细节错综复杂,且在任何时候都只有部分被理解。我们的计算机程序很少能永久令人满意地对它们建模。因此,即使我们的程序是精心手工制作的符号离散集合、互锁功能的马赛克,它们也在持续演化:随着我们对模型感知的深化、扩展、泛化,我们不断修改它们,直到模型最终在另一个我们仍在与之斗争的模型中达到一个亚稳态位置。与计算机编程相关联的那种兴奋感的源泉,是心智之内和计算机之上,以程序表达的机制持续展开,以及它们所产生的感知爆炸。如果艺术诠释了我们的梦想,那么计算机就以程序的形式执行它们!
尽管计算机能力强大,但它是一个严苛的工头。它的程序必须正确,我们想说的每一点都必须准确无误地表达。就像其他每一种符号活动一样,我们通过论证来说服自己程序的正确性。Lisp 本身可以被赋予语义(顺便说一句,这是另一个模型),如果程序的功能可以在谓词演算中描述,那么逻辑的证明方法就可以用来进行可接受的正確性论证。不幸的是,随着程序变得庞大和复杂——这几乎总是会发生——规范本身的充分性、一致性和正确性也变得值得怀疑,因此完整的形式化正确性论证很少伴随大型程序。由于大型程序是从小型程序生长而来的,关键在于我们要积累一套我们对正确性已有把握的标准程序结构——我们称之为习惯用法——并学会使用经过验证的组织技术将它们组合成更大的结构。这些技术在本书中有详细讨论,理解它们对于参与称为编程的普罗米修斯式事业至关重要。最重要的是,发现并掌握强大的组织技术可以加速我们创建大型、重要程序的能力。反过来,由于编写大型程序非常费力,这也激发我们发明新方法来减少需要塞进大型程序的功能和细节的量。
与程序不同,计算机必须服从物理定律。如果它们希望快速执行——每次状态变化几纳秒——它们只能将电子传输很短的距离(最多 1 1/2 英尺)。如此大量器件集中在空间中产生的热量必须被散除。人们在功能多样性和器件密度之间取得平衡,发展出了一门精妙的工程艺术。无论如何,硬件总是在比我们关心的编程层次更原始的层次上运行。将我们的 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 的入门教材进行比较。不要错误地认为这是一本只有麻省理工学院才能消化的教材,只有那里的怪才才适合。这正是任何一本关于 Lisp 编程的严肃书籍应有的样子,无论学生是谁、在哪里使用。
请注意,这是一本关于编程的教材,不同于大多数被用作人工智能工作准备的 Lisp 书籍。毕竟,软件工程和人工智能的关键编程关注点会随着所研究的系统变得更大而趋于融合。这就解释了为什么在人工智能领域之外,人们对 Lisp 的兴趣也在增长。
正如从其目标中可以预期的那样,人工智能研究产生了许多重要的编程问题。在其他编程文化中,这些问题的涌现会催生新的语言。实际上,在任何非常大的编程任务中,一个有用的组织原则是通过发明语言来控制和隔离任务模块内的交流。当我们接近系统中我们人类最常交互的边界时,这些语言往往会变得不那么原始。结果是,这样的系统包含复杂的语言处理功能,被重复多次实现。Lisp 具有如此简单的语法和语义,以至于解析可以被当作一项基本任务。因此,解析技术在 Lisp 程序中几乎不扮演角色,语言处理器的构建很少成为大型 Lisp 系统增长和变化速度的障碍。最后,正是这种语法和语义的简单性,构成了所有 Lisp 程序员所承受的负担和自由。任何超过几行的 Lisp 程序,都不可能不充满自主决定的功能。发明并适配;不适配就重新发明!我们举杯致敬那些在括号的嵌套中书写思想的 Lisp 程序员。
|
Alan J. Perlis New Haven, Connecticut |