Lisp 不同在哪
原文:What Made Lisp Different
作者:Paul Graham 发表:2001-12
译者:Claude(baoyu-translate)
2001 年 12 月(2002 年 5 月修订)
(本文是对 LL1 邮件列表(Lightweight Languages 1 会议邮件列表)上一些问题的回应。它现已被收入《nerd 的反击》。)
McCarthy(John McCarthy,Lisp 之父)在 1950 年代末设计 Lisp 时,它对当时已有的语言——其中最重要的是 Fortran——是一次激进的脱离。
Lisp 体现了 9 个新想法:
-
条件判断(conditional)。 条件判断就是 if-then-else 结构。今天我们觉得理所当然——它们是 McCarthy 在开发 Lisp 的过程中发明出来的。(Fortran 当时只有“条件 goto“,紧贴着底层硬件的分支指令。)McCarthy 当时身在 Algol 委员会,他把条件判断带进了 Algol——再从那儿散播到大多数其他语言。
-
函数类型。 在 Lisp 里,函数是一等公民对象——它们和整数、字符串等一样是一种数据类型,有字面表示,可以存进变量、可以作为参数传递,等等。
-
递归。 递归当然在 Lisp 之前就作为一个数学概念存在——但 Lisp 是第一门支持它的编程语言。(可以说,“把函数当一等公民“已经隐含了递归。)
-
关于变量的新概念。 在 Lisp 里,所有变量本质上都是指针。有类型的是值,不是变量——给变量赋值或绑定,意味着拷贝指针,而不是拷贝指针所指的内容。
-
垃圾回收。
-
由表达式构成的程序。 Lisp 程序是表达式的树——每一个都返回一个值。(在某些 Lisp 里,表达式可以返回多个值。)这与 Fortran 及其后大多数语言形成对比——后者区分表达式与语句。
Fortran 里有这种区分是很自然的——因为这门语言(在一门输入格式是打孔卡的语言里这一点毫不奇怪)是面向行的:你不能嵌套语句。所以你做数学时需要表达式,但让别的东西也返回值没意义——因为没有任何东西在等着接它。
这条限制随着块结构语言的出现而消失,但那时已经太晚——表达式与语句的区分根深蒂固了。它从 Fortran 蔓延到 Algol,再蔓延到两者各自的后裔。
当一门语言完全由表达式构成时,你可以任意组合表达式。你可以这样写(用 Arc 语法):
(if foo (= x 1) (= x 2))
也可以这样写:
(= x (if foo 1 2))
-
符号类型(symbol type)。 符号和字符串的不同之处在于——你可以比指针来测相等。
-
用“由符号构成的树“来表达代码的记号系统。
-
整门语言一直都在。 读入时(read-time)、编译时、运行时之间没有真正的区分。你可以在读入时编译或运行代码,可以在编译时读入或运行代码,可以在运行时读入或编译代码。
读入时运行代码让用户可以重编程 Lisp 自己的语法;编译时运行代码是宏的基础;运行时编译是 Lisp 用作 Emacs 这类程序的扩展语言的基础;而运行时读入则使程序能用 s-表达式相互通讯——这一点最近被以 XML 的形式重新发明了一遍。
Lisp 刚被发明时,所有这些想法和当时的日常编程实践相距甚远——后者大部分由 1950 年代末可用的硬件所决定。
随着时间推移,“默认语言”——以一系列流行语言为载体——逐步朝 Lisp 进化。1–5 现已普及;6 正开始出现在主流里;Python 有一种第 7 个的形式,虽然它没有专门的语法。第 8 个——加上第 9 个——正是让 Lisp 宏成为可能的东西,至今仍是 Lisp 独有的。也许是因为:(a) 它要求那些括号——或者别的同样糟糕的东西;(b) 如果你真的加上“力量的最后那一份“,你就不能再宣称自己发明了一门新语言——你只能宣称自己设计了 Lisp 的一种新方言 ;-)
虽然这种“按 Lisp 偏离别的语言随手凑出的方案多远来描述 Lisp“对今天的程序员有用,但这种描述方式很怪——大概不是 McCarthy 当年的想法。Lisp 不是为了修复 Fortran 的错误而设计的——它更多是一次“试图把计算公理化“的尝试的副产品。