

# 前言

当我还是学生的时候，我有幸见到了令人惊叹的艾伦·珀利斯，并与他交谈过几次。他和我有一个共同之处，那就是对两种截然不同的编程语言:Lisp 和 APL 的热爱和尊敬。追随他的脚步是一项艰巨的任务，尽管他开辟了一条出色的道路。尽管如此，我还是想重新审视一下他在这本书的原前言中所做的一个评论(并且，我建议你在读完这本书之前，先读一下紧接着这本书的前言)。100 个函数操作一个数据结构真的比 10 个函数操作 10 个数据结构好吗？

为了仔细回答这个问题，我们首先需要问一个数据结构是否是“通用的”:它能方便地完成那 10 个更专门化的数据结构的角色吗？

就此而言，我们也可以问:我们真的需要 100 个函数吗？有没有一个单一的通用功能可以完成所有其他功能的角色？

对于最后一个问题，令人惊讶的答案是“是”；构造一个函数只需要一点点技巧，这个函数接受(1)一个作为其他函数描述的数据结构，和(2)一个参数列表，当应用于给定的参数时，它的行为和其他函数完全一样。设计一个能够描述任何计算的数据结构并不复杂。本书第 4 章中描述了一个这样的数据结构(表达式和语句的标记列表表示，与将名称与值相关联的环境配对)和一个这样的通用函数(`apply`)。所以也许我们只需要一个函数和一个数据结构。

理论上是这样的。在实践中，我们发现，当人类构造计算的描述时，画出有助于我们组织代码结构的区别是很方便的，这样我们可以更好地理解它们。我相信 Perlis 说的不是计算能力，而是人类的能力和人类的局限性。

人类思维似乎擅长的一件事是给事物命名；我们有强大的联想记忆。给定一个名字，我们可以很快回忆起一些相关的事情。这就是为什么我们通常发现使用 lambda 微积分比使用组合微积分更容易；对于大多数人来说，解释 Lisp 表达式`(lambda (x) (lambda (y) (+ x y)))`或 JavaScript 表达式要容易得多

`x => y => x + y`比组合式表达

```js
((S ((S (K S)) ((S ((S (K S)) ((S (K K)) (K +)))) ((S (K K)) I)))) (K I))
```

即使有直接的结构对应，也很容易用五行 Lisp 代码表达出来。

因此，虽然原则上我们可以只使用一个通用函数，但我们更喜欢模块化我们的代码，给各个部分命名，并提及函数描述的名称，而不是不断地将描述本身提供给通用函数。

在我 1998 年的演讲“发展一门语言”中，我评论说一个好的程序员“不仅仅是写程序。一个好的程序员会建立一个工作词汇。”当我们设计和定义程序中越来越多的部分时，我们给这些部分起了名字，结果是我们有了更丰富的语言来编写剩下的部分。

但是我们也发现在数据结构之间进行区分并给它们命名是很自然的。

嵌套列表可能是一种通用的数据结构(值得注意的是，许多现代的和广泛使用的数据结构，如 HTML、XML 和 JSON，也是带括号的嵌套表示，只是比 Lisp 的裸括号稍微复杂一些)。还有许多函数，比如查找列表的长度，或者对列表中的每个元素应用一个函数并返回一个结果列表，这些函数在很多情况下都很有用。然而，当我在思考一个具体的计算时，我经常对自己说，“这两个东西的列表，我希望是一个人的名字和姓氏，但这两个东西的列表，我希望是一个复数的实部和虚部，另一个两个东西的列表，我会认为是一个分数的分子和分母。”换句话说，我进行了区分——在数据结构中明确表示这些区分可能是有用的，部分是为了防止错误，例如意外地将一个复数当作一个分数。(还是那句话，这是关于人的能力和人的局限性的评论。)

自从这本书的第一版在大约 40 年前出版以来，更多的组织数据的方法已经变得相对标准，尤其是“面向对象”的方法，许多语言，包括 JavaScript，都支持专门的数据结构，比如对象、字符串、堆和映射，以及各种内置的机制和库。但是在这样做的时候，许多语言放弃了对更通用的概念的支持。例如，Java 最初不支持一级函数，直到最近才加入了这些函数，从而大大增强了它的表达能力。

同样，APL 最初不支持一级函数，而且它最初的单一数据结构——任意维数的数组——不如通用数据结构有用，因为数组不能包含其他数组作为元素。APL 的最新版本支持匿名函数值和嵌套数组，这使得 APL 更具表现力。(APL 的最初设计确实有两个非常好的方面:适用于一种数据结构的一组全面的函数，以及为这些函数精心选择的一组名称。我说的不是那些搞笑的符号和希腊字母，而是 APL 程序员提到它们时用的口语词，像`shape`、`reshape`、`compress`、`expand`、`laminate`这样的词；这些不是符号的名字，而是它们所代表的功能的名字。Ken Iverson 很擅长为数组中的函数选择简短、易记、生动的名字。)

虽然 JavaScript 和 Java 一样，最初设计时考虑了对象和方法，但它也从一开始就融入了一流的函数，使用它的对象来定义通用数据结构并不困难。因此，JavaScript 并不像你想象的那样远离 Lisp，正如本期的计算机程序的结构和解释所展示的，它是一个很好的展示关键思想的替代框架。SICP 从来都不是一门编程语言；它为程序组织提供了强大的、通用的思想，这在任何语言中都是有用的。

Lisp 和 JavaScript 有什么共同点？将计算(代码加上一些相关数据)抽象为函数供以后执行的能力；在数据结构中嵌入对这些函数的引用的能力；根据参数调用函数的能力；区分能力(有条件执行)；一种方便的通用数据结构；对这些数据进行完全自动化的存储管理(考虑到其他因素，这似乎是显而易见的，直到您意识到许多广泛使用的编程语言都不具备这一点)；一大组用于在通用数据结构上操作的有用函数；以及使用通用数据结构来表示更专用的数据结构的标准策略。

因此，也许真相就在珀尔斯如此雄辩地假设的两个极端之间。也许最佳点更像是 40 个足够通用的函数，可以对通用数据结构(如列表)进行有效的操作，但也有 10 组 6 个函数，当我们从通用数据结构的 10 个专用视图中选择一个时，每个函数都是相关的。如果我们给这些函数和专门化的视图起个好名字，这是可以管理的。

当你阅读这本书时，请不仅注意编程语言的结构和它们是如何使用的，还要注意函数、变量和数据结构的名字。它们并不都像艾弗森为他的 APL 函数选择的名字那样简短而生动，但它们是以一种深思熟虑和系统的方式选择的，以增强您对整个程序结构的理解。

原语、组合方式、功能抽象、命名，以及通过区分以特定方式使用通用数据结构的约定:这些是优秀编程语言的基本构件。接下来，想象力和基于经验的良好工程判断可以完成剩下的工作。

——小盖伊·l·斯蒂尔，马萨诸塞州列克星敦，2021 年