Featured image of post ANSI Common Lisp 第一章

ANSI Common Lisp 第一章

介绍Lisp语言的特点和优势,探讨自底向上编程方法及其在现代软件开发中的应用。

📚 返回 Paul Graham 文章目录

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 /* C */ (defun sum (n) int sum(int n){ (let ((s 0)) int i, s = 0; (dotimes (i n s) for(i = 0; i < n; i++) (incf s i)))) s += i; return(s); }

如果你只需要做这样简单的事情,使用哪种语言其实并不重要。假设你想写一个函数,它接受一个数字n,并返回一个将其参数加上n的函数:

; Lisp (defun addn (n) #’(lambda (x) (+ x n)))

在C中addn会是什么样子?你根本无法写出这样的代码。

你可能会想,什么时候会需要做这样的事情?编程语言会教你不要想要它们无法提供的东西。你必须用语言来思考才能用它写程序,而很难想要你无法描述的东西。当我第一次开始写程序时——用Basic——我并不怀念递归,因为我不知道有这种东西。我用Basic思考。我只能想象迭代算法,所以为什么要怀念递归呢?

如果你不怀念词法闭包(这就是前面例子中创建的东西),暂时相信这一点:Lisp程序员一直在使用它们。很难找到一个不使用闭包的Common Lisp程序,无论它多长。到第112页时,你自己就会使用它们了。

而闭包只是我们在其他语言中找不到的抽象之一。Lisp的另一个独特特性,可能更有价值,是Lisp程序是用Lisp数据结构表达的。这意味着你可以写程序来写程序。人们真的想要这样做吗?是的——它们被称为宏,同样,有经验的程序员一直在使用它们。到第173页时,你就能写自己的宏了。

有了宏、闭包和运行时类型,Lisp超越了面向对象编程。如果你理解前面这句话,你可能不应该读这本书。你必须对Lisp相当了解才能明白为什么这是真的。但这不仅仅是空话。这是一个重要的观点,在第17章中,我们会在代码中明确地证明这一点。

第2-13章将逐步介绍你需要理解第17章代码的所有概念。你的努力将得到模棱两可的回报:你会觉得用C++编程就像有经验的C++程序员用Basic编程一样感到窒息。如果我们思考这种感觉的来源,也许会更有启发性。Basic对有C++经验的人来说是窒息的,因为有经验的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]

这是对那个时代最成功的系统之一的描述。

旧模型的问题在于它忽视了人类的局限性。在旧模型中,你是在赌规范不会包含严重缺陷,实现它们只是简单地将它们翻译成代码的问题。经验表明这是一个非常糟糕的赌注。更安全的赌注是认为规范会被误导,代码会充满错误。

这正是新的编程模型所假设的。与其希望人们不会犯错,它试图让错误的成本非常低。错误的成本是纠正它所需的时间。有了强大的语言和良好的编程环境,这个成本可以大大降低。编程风格可以更少地依赖计划,更多地依赖探索。

计划是一个必要的邪恶。它是对风险的回应:一项事业越危险,提前计划就越重要。强大的工具降低风险,因此减少了对计划的需求。你的程序设计可以受益于可能是最有用的信息来源:实现它的经验。

Lisp风格自1960年代以来一直在向这个方向进化。你可以用Lisp如此快速地写原型,以至于在旧模型中你甚至还没写完规范时,你就可以经历设计和实现的几次迭代。你不必太担心设计缺陷,因为你会更早发现它们。你也不必太担心错误。当你用函数式风格编程时,错误只能有局部影响。当你使用非常抽象的语言时,某些错误(例如悬空指针)不再可能,剩下的错误很容易找到,因为你的程序短得多。而且当你有交互式环境时,你可以立即纠正错误,而不是忍受漫长的编辑、编译和测试周期。

Lisp风格这样进化是因为它能产生结果。听起来很奇怪,更少的计划可能意味着更好的设计。技术史上充满了类似的案例。在15世纪,绘画也发生了类似的变化。在油画流行之前,画家使用一种叫做蛋彩画的媒介,它不能混合或覆盖。错误的成本很高,这倾向于使画家保守。然后出现了油画,随之而来的是风格的巨大变化。油画"允许重新思考”。[3] 这在处理像人体这样困难的主题时被证明是一个决定性的优势。

新媒介不仅使画家的生活更容易。它使一种新的、更雄心勃勃的绘画成为可能。Janson写道:

没有油画,佛兰德斯大师对可见现实的征服会更加有限。因此,从技术角度来看,他们也有资格被称为"现代绘画之父",因为油画从此成为画家的基本媒介。[4]

作为一种材料,蛋彩画并不比油画逊色。但油画的灵活性给想象力提供了更大的范围——这是决定性因素。

编程现在正在经历类似的变化。新媒介是"面向对象的动态语言"——简而言之,就是Lisp。这并不是说我们所有的软件都会在几年内用Lisp编写。从蛋彩画到油画的转变并非一夜之间发生;最初,油画只在主要的艺术中心流行,而且经常与蛋彩画结合使用。我们现在似乎处于这个阶段。Lisp在大学、研究实验室和一些前沿公司中使用。同时,从Lisp借鉴的想法越来越多地出现在主流中:交互式编程环境、垃圾回收和运行时类型,仅举几例。

更强大的工具正在消除探索的风险。这对程序员来说是好消息,因为这意味着我们将能够承担更雄心勃勃的项目。油画的使用肯定有这种效果。采用它之后的时期是绘画的黄金时代。已经有迹象表明编程领域正在发生类似的事情。

可在以下地址购买:http://www.amazon.com/exec/obidos/ASIN/0133708756

注释

[1] McCarthy, John. Recursive Functions of Symbolic Expressions and their Computation by Machine, Part I. CACM, 3:4 (April 1960), pp. 184-195.

McCarthy, John. History of Lisp. In Wexelblat, Richard L. (Ed.) History of Programming Languages. Academic Press, New York, 1981, pp. 173-197.

在印刷时,这两篇文章都可以在 http://www-formal.stanford.edu/jmc/ 找到。

[2] Brooks, Frederick P. The Mythical Man-Month. Addison-Wesley, Reading (MA), 1975, p. 16.

快速原型不仅是一种更快或更好地写程序的方式。它是一种可能根本不会被写出来的程序的写作方式。

即使是最雄心勃勃的人也会对大型事业望而却步。如果一个人能说服自己(无论多么似是而非)这不会太费事,开始做某事会更容易。这就是为什么这么多大事都是从小事开始的。快速原型让我们从小处开始。

[3] Murray, Peter and Linda. The Art of the Renaissance. Thames and Hudson, London, 1963, p. 85.

[4] Janson, W. J. History of Art, 3rd Edition. Abrams, New York, 1986, p. 374.

这个类比当然只适用于在面板上,后来在画布上完成的画作。壁画继续用湿壁画技法完成。我也不想暗示绘画风格是由技术变革驱动的;相反似乎更接近真相。

英文版:sep.turbifycdn.com/ty/cdn/paulgraham/acl1.txt?t=1743250771&|中文版:HiJiangChuan.com/paulgraham/003-Chapter-1-of-Ansi-Common-Lisp

📚 返回 Paul Graham 文章目录

更新记录: