ANSI Common Lisp 简介
ANSI Common Lisp 简介
(这是Paul Graham的《ANSI Common Lisp》一书的第一章。版权1995年,Prentice-Hall。)
介绍
John McCarthy和他的学生于1958年开始在第一个Lisp实现上工作。除了Fortran之外,Lisp是仍在使用的最古老的语言。[1] 更值得注意的是,它仍然处于编程语言技术的前沿。了解Lisp的程序员会告诉你,这种语言有某种独特之处。
Lisp的独特之处部分在于它被设计为可进化的。你可以使用Lisp来定义新的Lisp操作符。当新的抽象变得流行时(例如面向对象编程),事实证明在Lisp中实现它们总是很容易的。像DNA这样的语言不会过时。
新工具
为什么学习Lisp?因为它让你做其他语言做不到的事情。如果你只是想写一个返回小于n的数字之和的函数,在Lisp和C中看起来会非常相似:
; Lisp
(defun sum (n)
(let ((s 0))
(dotimes (i n s)
(incf s i))))
/* C */
int sum(int n){
int i, s = 0;
for(i = 0; i < n; i++)
s += i;
return(s);
}
如果你只需要做这样的简单事情,你使用哪种语言并不重要。假设相反,你想写一个函数,它接受一个数字n,并返回一个将n加到其参数上的函数:
; Lisp
(defun addn (n)
#'(lambda (x) (+ x n)))
addn在C中是什么样的?你根本写不出来。
你可能会想知道,什么时候有人会想做这样的事情?编程语言教会你不要渴望它们不能提供的东西。你必须用一种语言思考才能用其编写程序,很难渴望你无法描述的东西。当我开始编写程序时——使用Basic——我不想念递归,因为我不知道有这样的东西。我用Basic思考。我只能构想迭代算法,为什么要想念递归?
如果你不怀念词法闭包(这就是前面例子中制作的东西),暂时相信Lisp程序员一直使用它们。很难找到任何长度的Common Lisp程序不利用闭包。到第112页,你自己也会使用它们。而闭包只是我们在其他语言中找不到的抽象之一。
Lisp的另一个独特特征,可能更有价值,是Lisp程序被表达为Lisp数据结构。这意味着你可以编写编写程序的程序。人们真的想这样做吗?是的——它们被称为宏,再次,经验丰富的程序员一直使用它们。到第173页,你将能够编写自己的宏。
有了宏、闭包和运行时类型,Lisp超越了面向对象编程。如果你理解了前面的句子,你可能不应该读这本书。你必须很好地了解Lisp才能明白为什么这是真的。但这不仅仅是文字。这是一个重要的一点,它的证明在第17章的代码中非常明确地展示出来。
第2-13章将逐步介绍你理解第17章代码所需的所有概念。你努力的回报将是一个明确的回报:你会感觉在C++中编程就像经验丰富的C++程序员在Basic中编程一样令人窒息。
如果我们考虑这种感觉来自哪里,可能会更令人鼓舞。对于习惯C++的人来说,Basic令人窒息,因为经验丰富的C++程序员知道不可能在Basic中表达的技术。同样,学习Lisp教给你的不仅仅是新语言——它教你新的、更强大的思考程序的方式。
新技术
如前一节所解释的,Lisp给你提供了其他语言没有提供的工具。但故事远不止这些。分开来看,Lisp带来的新东西——自动内存管理、显式类型、闭包等等——每一个都使编程更加容易。结合起来,它们形成了一个临界质量,使新的编程方式成为可能。
Lisp被设计为可扩展的:它让你自己定义新的操作符。这是可能的,因为Lisp语言是由与你自己的程序相同的函数和宏组成的。所以扩展Lisp并不比在其中编写程序更困难。事实上,扩展语言如此容易(如此有用),以至于它成为标准实践。当你将程序向语言方向编写时,你将语言向程序方向构建。你既自底向上,又自顶向下地工作。
几乎任何程序都可以从语言被定制以满足其需求中受益,但程序越复杂,自底向上编程变得越有价值。自底向上程序可以写成一系列层,每一层都作为上一层的一种编程语言。TeX是最早以这种方式编写的程序之一。
你可以在任何语言中自底向上编写程序,但Lisp是这种风格最自然的载体。自底向上编程自然地导致可扩展软件。如果你将自底向上编程的原则贯彻到你程序的最上层,那么那一层就成为用户的编程语言。
因为可扩展性的概念在Lisp中根深蒂固,它成为编写可扩展软件的理想语言。1980年代最成功的三个程序提供Lisp作为扩展语言:Gnu Emacs、Autocad和Interleaf。
自底向上工作也是获得可重用软件的最佳方式。编写可重用软件的本质是将一般与特定分开,而自底向上编程本质上创造了这种分离。你不是将所有精力用于编写单一的、整体的应用程序,而是将部分精力用于构建一种语言,部分用于在其上编写(相对较小的)应用程序。对这个应用程序特定的东西将集中在最上层。下面的层将为编写这样的应用程序形成一种语言——还有什么比编程语言更可重用?
Lisp不仅让你编写更复杂的程序,而且让你更快地编写它们。Lisp程序往往很短——语言给你更大的概念,所以你不必使用那么多。正如Frederick Brooks指出的那样,编写程序所需的时间主要取决于其长度。所以仅这一事实就意味着Lisp程序需要更少的时间来编写。Lisp的动态特性放大了这种效果:在Lisp中,编辑-编译-测试周期如此之短,以至于编程是实时的。
更大的抽象和交互式环境可以改变组织开发软件的方式。“快速原型"这个词描述了始于Lisp的一种编程:在Lisp中,你通常可以在比编写规范更少的时间内编写原型。更重要的是,这样的原型可以如此抽象,以至于它比用英语编写的规范更好。而且Lisp允许你从原型平滑过渡到生产软件。当Common Lisp程序以速度为目标编写并由现代编译器编译时,它们运行得与用任何其他高级语言编写的程序一样快。
除非你已经很好地了解Lisp,否则这个介绍可能看起来是一系列宏大且可能毫无意义的声明。Lisp超越面向对象编程?你将语言向你的程序构建?Lisp编程是实时的?这样的陈述意味着什么?目前,这些主张像空湖一样。当你了解更多Lisp的实际特性,看到工作程序的例子时,它们将充满真实的经验并呈现出明确的形状。
新方法
本书的目标之一不仅是解释Lisp语言,还有Lisp使之成为可能的新编程方法。这种方法是你将来会看到更多的。随着编程环境变得更强大,语言变得更抽象,Lisp编程风格正在逐渐取代旧的计划-实施模型。
在旧模型中,错误不应该发生。详尽的规范,在事先痛苦地制定出来,应该确保程序完美工作。理论上听起来不错。不幸的是,规范都是由人类编写和实施的。实际上,结果是计划-实施方法效果不佳。作为OS/360项目的经理,Frederick Brooks对传统方法非常熟悉。他也了解其结果:
任何OS/360用户很快就能意识到它应该有多好…此外,产品迟到了,它比计划需要更多内存,成本是估计的好几倍,而且在第一次发布后的几个版本中表现不佳。[2]
这是对其时代最成功系统之一的描述。旧模型的问题在于它忽略了人类的局限性。在旧模型中,你是在赌规范不会包含严重缺陷,实现它们将是将它们翻译成代码的简单事情。经验表明,这是一个非常糟糕的赌注。更安全的赌注是规范会被误导,代码会充满错误。
这正是新编程模型所假设的。它不是希望人们不会犯错误,而是试图使错误的成本非常低。错误的成本是纠正它所需的时间。使用强大的语言和良好的编程环境,这个成本可以大大降低。编程风格可以较少依赖规划,较多依赖探索。
规划是必要的邪恶。它是对风险的回应:事业越危险,提前规划就越重要。强大的工具降低风险,因此减少了对规划的需求。然后,你的程序设计可以从可能是最有用的信息来源中受益:实现它的经验。
自1960年代以来,Lisp风格一直在向这个方向发展。你可以在Lisp中如此快速地编写原型,以至于在旧模型中甚至完成编写规范之前,你可以经历几次设计和实现的迭代。你不必太担心设计缺陷,因为你更快地发现它们。你也不必太担心错误。当你以函数式风格编程时,错误只能有局部影响。当你使用非常抽象的语言时,某些错误(例如悬空指针)不再可能,而剩下的错误很容易找到,因为你的程序短得多。而且当你有交互式环境时,你可以立即纠正错误,而不是忍受漫长的编辑、编译和测试周期。
Lisp风格之所以这样发展是因为它产生结果。听起来很奇怪,较少的计划可能意味着更好的设计。技术史上充满了类似的案例。十五世纪绘画发生了类似的变化。在油画流行之前,画家使用一种称为蛋彩的介质,不能混合或覆盖。错误的成本很高,这往往使画家保守。然后来了油画,随之而来的是风格的巨大变化。油画"允许第二次思考。"[3] 这在处理像人体这样困难的题材时被证明是决定性的优势。新媒介不仅仅使画家的生活更容易。它使一种新的、更雄心勃勃的绘画成为可能。Janson写道:
没有油画,佛兰德大师对可见现实的征服将受到更多限制。因此,从技术角度来看,他们也应被称为"现代绘画之父”,因为油画自此一直是画家的基本媒介。[4]
作为一种材料,蛋彩并不比油画逊色。但油画的灵活性给了想象力更大的空间——这是决定性因素。
编程现在正在经历类似的变化。新媒介是"面向对象的动态语言"——一个词:Lisp。这并不是说我们所有的软件将在几年内都用Lisp编写。从蛋彩到油画的转变不是一夜之间发生的;最初,油画只在领先的艺术中心流行,经常与蛋彩结合使用。我们现在似乎处于这个阶段。Lisp在大学、研究实验室和少数领先公司中使用。同时,从Lisp借用的想法越来越多地出现在主流中:交互式编程环境、垃圾收集和运行时类型,仅举几例。
更强大的工具正在消除探索的风险。这对程序员来说是好消息,因为这意味着我们将能够承担更雄心勃勃的项目。油画的使用当然产生了这种效果。采用它之后的时期是绘画的黄金时代。已经有迹象表明编程中正在发生类似的事情。
可在以下网址获得:http://www.amazon.com/exec/obidos/ASIN/0133708756
注释
[1] McCarthy, John. 符号表达式的递归函数及其机器计算,第一部分。CACM, 3:4 (1960年4月), pp. 184-195.
McCarthy, John. Lisp历史。在Wexelblat, Richard L. (编) 编程语言历史。Academic Press, 纽约, 1981, pp. 173-197.
印刷时两者都在http://www-formal.stanford.edu/jmc/上可用。
[2] Brooks, Frederick P. 神话般的人月。Addison-Wesley, 雷丁(马萨诸塞州), 1975, p. 16.
快速原型不仅是更快或更好地编写程序的方式。它是一种编写否则可能根本不会被编写的程序的方式。即使是最雄心勃勃的人也会回避重大的事业。如果一个人能说服自己(不管多么站不住脚)那不会是太多工作,开始一些事情会更容易。这就是为什么这么多大事始于小事。快速原型让我们从小处开始。
[3] Murray, Peter和Linda。文艺复兴艺术。Thames and Hudson, 伦敦, 1963, p. 85.
[4] Janson, W. J. 艺术史,第三版。Abrams, 纽约, 1986, p. 374.
当然,这个比喻只适用于在面板上和后来在画布上完成的绘画。壁画继续用湿壁画完成。我也不想暗示绘画风格是由技术变革驱动的;相反似乎更接近真实。