(2002年5月修订)(本文是为了回应 LL1 邮件列表上的一些问题而写。现已并入《黑客与画家》中的《书呆子的复仇》一文。)
(rev. May 2002) (This article came about in response to some questions on the LL1 mailing list. It is now incorporated in Revenge of the Nerds.)
当麦卡锡在 20 世纪 50 年代后期设计 Lisp 时,它与当时已有的语言相比是一次彻底的叛逆,而当时最重要的一种语言是 Fortran。
When McCarthy designed Lisp in the late 1950s, it was a radical departure from existing languages, the most important of which was Fortran.
Lisp 具体体现了九个新思想:
Lisp embodied nine new ideas:
1. 条件分支。 条件分支就是 if-then-else 结构。我们现在对此习以为常。它们是麦卡锡在开发 Lisp 的过程中发明的。(当时的 Fortran 只有条件跳转,紧密基于底层硬件的跳转指令。)作为 Algol 委员会成员的麦卡锡,将条件分支引入了 Algol,随后它便传播到了大多数其他语言中。
1. Conditionals. A conditional is an if-then-else construct. We take these for granted now. They were invented by McCarthy in the course of developing Lisp. (Fortran at that time only had a conditional goto, closely based on the branch instruction in the underlying hardware.) McCarthy, who was on the Algol committee, got conditionals into Algol, whence they spread to most other languages.
2. 函数类型。 在 Lisp 中,函数是一等公民——它们和整数、字符串等一样是一种数据类型,有字面量表示法,可以存放在变量中,可以作为参数传递,等等。
2. A function type. In Lisp, functions are first class objects-- they're a data type just like integers, strings, etc, and have a literal representation, can be stored in variables, can be passed as arguments, and so on.
3. 递归。 在 Lisp 出现之前,递归当然作为数学概念已经存在,但 Lisp 是第一个支持递归的编程语言。(可以说,将函数作为一等公民,其中就隐式包含了解析递归的要求。)
3. Recursion. Recursion existed as a mathematical concept before Lisp of course, but Lisp was the first programming language to support it. (It's arguably implicit in making functions first class objects.)
4. 变量的新概念。 在 Lisp 中,所有变量本质上都是指针。有类型的是值,而不是变量;赋值或绑定变量意味着复制指针,而不是复制指针所指向的内容。
4. A new concept of variables. In Lisp, all variables are effectively pointers. Values are what have types, not variables, and assigning or binding variables means copying pointers, not what they point to.
5. 垃圾回收。
5. Garbage-collection.
6. 由表达式组成的程序。 Lisp 程序是表达式构成的树,每个表达式都返回一个值。(在某些 Lisp 方言中,表达式可以返回多个值。)这与 Fortran 以及大多数后来的语言截然不同,后者区分了“表达式”(expressions)和“语句”(statements)。
6. Programs composed of expressions. Lisp programs are trees of expressions, each of which returns a value. (In some Lisps expressions can return multiple values.) This is in contrast to Fortran and most succeeding languages, which distinguish between expressions and statements.
在 Fortran 中存在这种区分是很自然的,因为该语言是面向行的(这在输入格式为打孔卡片的语言中并不令人意外)。你无法嵌套语句。因此,虽然进行数学计算需要表达式,但让其他任何东西返回值都没有意义,因为没有地方可以接收这个返回值。
It was natural to have this distinction in Fortran because (not surprisingly in a language where the input format was punched cards) the language was line-oriented. You could not nest statements. And so while you needed expressions for math to work, there was no point in making anything else return a value, because there could not be anything waiting for it.
这种限制随着块结构语言的出现而消失了,但那时已经太迟了。表达式和语句之间的界限已经根深蒂固。它从 Fortran 传播到 Algol,进而传给了它们两者的后代。
This limitation went away with the arrival of block-structured languages, but by then it was too late. The distinction between expressions and statements was entrenched. It spread from Fortran into Algol and thence to both their descendants.
当一个语言完全由表达式组成时,你可以任意组合它们。你可以写(使用 Arc 语法):
When a language is made entirely of expressions, you can compose expressions however you want. You can say either (using Arc syntax)
(if foo (= x 1) (= x 2))
(if foo (= x 1) (= x 2))
或者
or
(= x (if foo 1 2))
(= x (if foo 1 2))
7. 符号类型。 符号与字符串的不同之处在于,你可以通过比较指针来判断它们是否相等。
7. A symbol type. Symbols differ from strings in that you can test equality by comparing a pointer.
8. 用符号树表示代码的记法。
8. A notation for code using trees of symbols.
9. 整个语言随时可用。 读取期、编译期和运行期之间没有绝对的界限。你可以在读取代码时进行编译或运行,在编译时读取或运行代码,在运行时读取或编译代码。
9. The whole language always available. There is no real distinction between read-time, compile-time, and runtime. You can compile or run code while reading, read or run code while compiling, and read or compile code at runtime.
在读取期运行代码允许用户重新定义 Lisp 的语法;在编译期运行代码是宏的基础;在运行时编译是 Lisp 在 Emacs 等程序中作为扩展语言的基础;而在运行时读取则使程序能够通过 S-表达式进行通信,这一想法最近被重新发明成了 XML。
Running code at read-time lets users reprogram Lisp's syntax; running code at compile-time is the basis of macros; compiling at runtime is the basis of Lisp's use as an extension language in programs like Emacs; and reading at runtime enables programs to communicate using s-expressions, an idea recently reinvented as XML.
在 Lisp 刚被发明出来时,所有这些思想都与主流编程实践相去甚远,当时的编程实践很大程度上是由 20 世纪 50 年代末可用的硬件所决定的。
When Lisp was first invented, all these ideas were far removed from ordinary programming practice, which was dictated largely by the hardware available in the late 1950s.
随着时间的推移,在一代代流行语言中体现出来的“默认语言标准”正逐渐向 Lisp 演进。第 1 到第 5 点现在已经普及。第 6 点开始在主流语言中出现。Python 拥有第 7 点的一种形式,尽管似乎没有任何专门的语法。而第 8 点(与第 9 点配合)是让 Lisp 宏成为可能的核心,到目前为止依然是 Lisp 所独有的,这或许是因为:(a) 它需要那些括号,或者其他同样糟糕的东西;(b) 如果你加上这最后一点威力,你便无法再宣称自己发明了一门新语言,而只能说自己设计了 Lisp 的一个新方言 ;-)
Over time, the default language, embodied in a succession of popular languages, has gradually evolved toward Lisp. 1-5 are now widespread. 6 is starting to appear in the mainstream. Python has a form of 7, though there doesn't seem to be any syntax for it. 8, which (with 9) is what makes Lisp macros possible, is so far still unique to Lisp, perhaps because (a) it requires those parens, or something just as bad, and (b) if you add that final increment of power, you can no longer claim to have invented a new language, but only to have designed a new dialect of Lisp ; -)
虽然通过对比其他语言采用的各种权宜之计来描述 Lisp 对今天的程序员很有用,但这样做其实有点奇怪。麦卡锡当时大概不是这么想的。Lisp 的设计初衷并不是为了修正 Fortran 的错误,它更多是一次尝试将计算公理化的副产品。
Though useful to present-day programmers, it's strange to describe Lisp in terms of its variation from the random expedients other languages adopted. That was not, probably, how McCarthy thought of it. Lisp wasn't designed to fix the mistakes in Fortran; it came about more as the byproduct of an attempt to axiomatize computation.