# 前言

> 原文：[Foreword](https://sourceacademy.org/sicpjs/foreword02)
> 
> 译者：[飞龙](https://github.com/wizardforcel)
> 
> 协议：[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)

我有幸在我还是学生的时候见到了了不起的 Alan Perlis，并和他交谈了几次。他和我共同深爱和尊重两种非常不同的编程语言：Lisp 和 APL。跟随他的脚步是一项艰巨的任务，尽管他开辟了一条优秀的道路。尽管如此，我想重新审视他在这本书的原始前言中所做的一条评论（请阅读*他的*前言，它紧随在这个前言之后，然后再完成这个前言）。是否真的比起有 10 个函数作用于 10 个数据结构，有 100 个函数作用于一个数据结构更好？

要仔细回答这个问题，我们首先需要问这个数据结构是否“通用”：它是否可以方便地满足那 10 个更专门的数据结构的作用？

就此而言，我们还可以问：我们真的需要 100 个函数吗？是否有一个单一的通用函数可以满足所有其他函数的作用？

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

理论上是这样的。实际上，我们发现方便的是划分出有助于我们作为构建计算描述的人类来组织代码结构，以便更好地理解它们的区别。我相信 Perlis 所说的不是关于计算能力，而是关于人类的能力和局限性。

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

`x => y => x + y`比组合表达式更容易。

((S ((S (K S)) ((S ((S (K S)) ((S (K K)) (K +)))) ((S (K K)) I)))) (K I))

即使有直接的结构对应，可以用五行 Lisp 代码轻松表达。

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

在我 1998 年的演讲“语言的成长”中，我评论说，一个优秀的程序员“不仅仅是编写程序。一个优秀的程序员建立了一个工作词汇。”当我们设计和定义程序的更多部分时，我们给这些部分命名，结果是我们有了一个更丰富的语言来编写其余的部分。

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

嵌套列表可能是一种通用的数据结构（值得注意的是，许多现代和广泛使用的数据结构，如 HTML、XML 和 JSON，也是括号嵌套表示，只是比 Lisp 的裸括号稍微复杂一点）。还有许多函数，比如找到列表的长度或将函数应用于列表的每个元素并得到结果列表，这些函数在各种情况下都很有用。然而，当我考虑特定的计算时，我经常对自己说：“我期望这个两个元素的列表是名字和姓氏，但我期望那个两个元素的列表是复数的实部和虚部，而我将把另一个两个元素的列表视为分数的分子和分母。”换句话说，我在区分——并且在数据结构中明确表示这些区分可能是有用的，部分原因是为了防止错误，比如意外地将复数视为分数。（再次强调，这是关于人类能力和人类局限性的评论。）

自本书第一版写成以来，几乎已经有将近四十年的时间，许多组织数据的方式已经相对标准化，特别是“面向对象”的方法，许多语言，包括 JavaScript，都支持对象、字符串、堆和映射等专门的数据结构，并具有各种内置机制和库。但在这样做的过程中，许多语言放弃了对更一般、通用概念的支持。例如，Java 最初也不支持一流函数，直到相对较近的时候才将其纳入，大大增强了其表达能力。

APL 最初也不支持一流函数，而且它最初的单一数据结构——任意维度的数组——并不像通用数据结构那样方便使用，因为数组不能包含其他数组作为元素。最近的 APL 版本确实支持匿名函数值和嵌套数组，这使得 APL 的表达能力大大增强。（APL 的原始设计确实有两个非常好的地方：一个是适用于该数据结构的全面函数集，另一个是非常合适的函数名称集。我说的不是有趣的符号和希腊字母，而是 APL 程序员在提到它们时使用的口头词汇，比如`shape`、`reshape`、`compress`、`expand`和`laminate`；这些名称不是指代符号，而是指代它们所代表的函数。Ken Iverson 非常擅长为数组函数选择简短、易记、生动的名称。）

虽然 JavaScript 和 Java 一样最初设计时考虑了对象和方法，但它从一开始就纳入了一流函数，并且使用其对象定义通用数据结构并不困难。因此，JavaScript 并不像你想象的那样远离 Lisp，正如《计算机程序的构造和解释》的这一版本所展示的，它是一个呈现关键思想的良好替代框架。*SICP*从来不是关于一种编程语言；它提出了有用于任何语言的强大、通用的程序组织思想。

Lisp 和 JavaScript 有什么共同之处？抽象计算的能力（代码加上一些相关数据）以便以后作为函数进行执行；在数据结构中嵌入对这些函数的引用的能力；对参数调用函数的能力；区分的能力（条件执行）；一个方便的通用数据结构；完全自动的数据存储管理（鉴于其他一切，这似乎是理所当然的，直到你意识到许多广泛使用的编程语言没有它）；一大套对通用数据结构进行操作的有用函数；以及使用通用数据结构表示更专门的数据结构的标准策略。

所以也许真相在 Perlis 如此雄辩地提出的两个极端之间。也许最佳点更像是 40 个足够通用地在通用数据结构（如列表）上有用地操作的函数，但也有 10 组每组 6 个函数，当我们从 10 个专门的视角看待通用数据结构时是相关的。如果我们给这些函数和专门视角起好名字，这是可以管理的。

当你阅读这本书时，请不仅注意编程语言的构造和它们的使用方式，还要注意给函数、变量和数据结构起的*名称*。它们并不都像 Iverson 为他的 APL 函数选择的那样简短和生动，但它们是以一种有意识和系统的方式选择的，以增强你对整体程序结构的理解。

原语、组合方式、函数抽象、命名以及使用通用数据结构的约定方式来专门区分：这些是一个良好编程语言的基本构建块。从那里开始，想象力和基于经验的良好工程判断可以做剩下的事情。

—Guy L. Steele Jr.，马萨诸塞州列克星敦，2021
