转:书呆子的报复

原文链接:http://flyingapplet.spaces.live.com/blog/cns!F682AFBD82F7E261!375.entry

书呆子的报复[译]

[译注]本文译自Paul Graham的文章“Revenge of the Nerds”。由于译者对Lisp一窍不通,所以不保证完全理解原作者的观点,更不保证能够全面准确地表达其观点。如果你觉得上面这段话有点白痴,看原文吧。

书呆子的报复

2002年5月

“我们争取的是C++程序员。我们已经成功地把很多人从转向Lisp的半路上给拉过来了。”
- Guy Steele,Java规范作者之一

软件行业里一直有两股力量在争斗:酷想法的学院派和同样可怕的酷头发的老板。每个人都知道酷头发的老板指哪些人,对吧?我想大多数做技术的不仅认识这个卡通人物,而且知道自己公司里面哪些人可以和这个卡通人物对号入座。

那些酷头发的老板们的身上神奇地结合了两种品质,这两种品质各自都很常见,但很少同时出现在同一个人身上:(1)对技术完全彻底一窍不通;(2)对技术有很强的主见。

比方说,你要写一个软件,酷头发的老板完全不懂软件,对编程语言也一无所知,但是他就是知道你应该有什么编程语言来开发。真的,他觉得你应该用Java开发。

他为什么这样想呢?让我们研究一下酷头发的老板的大脑的思维方式,他是这样想的:Java是标准,肯定是标准,因为天天都可以在媒体上看到。既然是标准,用它就不会给自己带来麻烦。同时市面上总是有很多Java程序员,所以如果我手下的程序员辞职了(不知道什么在捣鬼,我手下的程序员老有人辞职),我很容易找到人替代他们。

嗯,听起来不是完全没有道理。但是,这种思路基于一个隐含的假定,而且这个假定不成立。酷头发的老板们相信所有的编程语言都是基本等价的。如果真是这样他就对了,如果所有编程语言都是等价的,当然用大家都在用的语言。

但是所有的语言都不是等价的,其实不用分析语言之间的具体差异就可以证明这一点。如果1992年你问酷头发的老板应该有什么语言开发软件,他会同样毫不犹豫地告诉你答案。应该用C++开发软件。如果语言都是等价的,酷头发的老板的观点为什么会改变呢。更进一步讲,开发Java的人们还有什么必要创造一门新语言呢。

通常情况下,如果你创造一门新的语言,那肯定是因为它可以在某些方面优于已有的方法。实际上,Gosling在第一版Java白皮书中明确指出设计Java是为了解决C++语言的一些问题。所以现在你知道了:不是所有语言都是等价的。如果你顺着酷头发的老板的思路看到Java,然后沿着 Java的历史看到其来源,你就会发现自相矛盾之处了。

那么谁对谁错呢?是James Gosling,还是酷头发的老板?不用说当然Gosling是对的。对于某些问题,有的语言确实比其它语言好。下面我们来看一些有意思的问题。Java被设计得在某些问题上比C++好。哪些问题呢?什么情况下Java好,什么情况下C++好?有没有某些场景下其它的语言比它们俩都好?

一旦你开始考虑这个问题,你就要面临一系列头痛的事情。假如酷头发的老板要全面思考这个问题,那会让他的脑袋炸开。而只要他继续认定所有的语言都是等价的,他就可以选择最流行的,流行实际上是一个关于时尚的问题,所以就连他也差不多能找到正确答案。但如果语言是有差别的,那么他就要解决两个联立方程,在两件他都不懂的事情之间找到最佳平衡:评估二十多种可用的语言对于要解决的问题的适宜度;分析对每个语言找到合适的库和程序员等等的可能性。如果这就是推开门之后要面临的问题,酷头发的老板不愿意打开这扇门就一点都不奇怪了。

所有编程语言都等价这一信念的缺点是它不正确。不过它的优点是它可以让你的生活简单很多。我想这就是这个信念如此流行的主要原因。它是个令人舒服的想法。

我们知道Java一定好极了,因为它就是那个最新最酷的编程语言。对不对?如果你从远处看编程语言的世界,似乎Java是最新的东西。(如果距离足够远,你唯一能看到的就是Sun花钱买的巨大的闪光的广告牌。)但如果走近了看,你会发现酷有很多种维度。在黑客圈子里,有个叫Perl的语言被大家公认比Java要酷多了。比如Slashdot就是用Perl生成的。你不太可能发现那帮人用Java Server Pages。还有一个更新的叫Python的语言,它的使用者认为Perl比较土。还有更多别的的语言。

如果按Java,Perl,Python这个顺序考察这三个语言,你会发现一个有趣的模式,至少Lisp黑客会发现这个模式。每个语言都更象Lisp。Python甚至复制了一些很多Lisp黑客们认为是错误的特征。简单的Lisp程序可以一行一行地翻译成为Python。现在是2002 年,编程语言终于追赶上1958年代了。
追赶数学

我是说Lisp最早是John McCarthy于1958年发明的,现在流行的编程语言只不过追赶上了他当时发展的一些想法。

这怎么可能呢?难道计算机技术不是变化很快的东西吗?比方说,1958年的计算机是冰箱大小的庞然大物[1],而计算能力跟现在的手表差不多。怎么可能那么老的技术还值得关注,更不用说优于最新的技术发展了?

我来告诉你怎么回事。这是因为Lisp并不是要设计成一门编程语言,至少不是现在意义上的编程语言。今天我们所说的编程语言是指我们用来告诉计算机怎么做的东西。McCarthy后来确实倾向于开发这种意义上的编程语言,不过最终的Lisp是基于他当时作的理论研究—关于定义图灵机的一种更方便的替代品的努力。正如McCarthy后来所说的,

展示Lisp比图灵机更精巧的另一种方式是写一个比通用图灵机描述更简洁更易于理解的通用Lisp函数。这就是Lisp函数eval……,它的功能是计算Lisp表达式的值……。实现eval需要发明一种可以把Lisp函数表示为Lisp数据的表示法,这个表示法被设计用于纸上表达,根本没有想过用于在实践中表达Lisp程序。

后来发生了一件事情,1958年某个时候,McCarthy的一个研究生Steve Russell看到eval的定义后意识到如果把它翻译成为机器语言,他就得到了一个Lisp解释器[2]。

这在当时是个大意外。后来McCarthy在一次访谈中是这样说的:

Steve Russell说我们为什么不编程实现这个eval函数呢……,我对他说,嚯嚯,你把理论和实践混淆了,这个eval是用来读的,不是用来计算的。但他还是继续做并完成了。也就是说,他把我论文里面的eval函数编译成了[IBM] 704机器语言,修正了错误,然后就到处把它当Lisp解释器推广,当然这确实是Lisp解释器。在那个时刻,Lisp在本质上就变得跟现在的Lisp差不多了……

我想在几个星期的时间内McCarthy突然发现他的理论研究转变成为了一门实际的编程语言—比他预想的强大很多的语言。

所以对于为什么50年代的语言还没有过时的简单解释就是它不是技术,而是数学,数学是不会陈旧的。对于Lisp的正确的类比不是50年代的硬件,而是类似于快速排序算法这样的东西,60年代发现的快速排序算法现在仍然是最快的通用排序算法。

还有一门语言从50年代至今依然生存着,那就是Fortran,它代表了一种截然相反的语言设计方法。Lisp是一种理论,被意外地变成了一门编程语言。Fortran被一开始就要开发成为一门编程语言,但是现在我们认为它是一门非常低级的语言。

1956年开发的Fortran I是跟今天的Fortran显著不同的动物。Fortran I基本上是带数学计算的汇编语言,比如里面没有子程序,只有分支。你都可以说今天的Fortran更接近Lisp,而不是Fortran I。

Lisp和Fortran就像两颗不同的演化树的主干,一个扎根于数学,另一个扎根于计算机体系结构。这两棵树一直趋于聚合。Lisp一出来就功能强大,在后续的二十年中越来越快。所谓的主流语言一出来就很快,在后续的四十年中逐渐强化功能,终于到现在其中最先进的算比较接近Lisp了。接近,但还是少了一些东西……
Lisp的独特之处

Lisp刚一出现就包含了九个新思想。有些思想今天大家都习以为常了,另外一些在比较高级的语言中才能看到,还有两个只有在Lisp才有。按照主流语言的接受次序来列出这九个思想就是:

   1.条件分支。

条件分支是一个if-then-else结构。今天我们对这个已经习以为常了,但是Fortran I没有。它只有条件goto,非常接近底层机器指令。

   2.函数类型。

在Lisp中,函数也是一个类型,跟整数或者字符串一样。它有文字表示,可以存储到变量中,可以作为参数传递等等。

   3.递归。

Lisp是第一个支持递归的编程语言。

   4.动态类型。

在Lisp里面所有的变量实际上都是指针。变量没有类型,值才有,对变量赋值或者绑定变量意味着复制指针,而不是它们指向的内容。

   5.垃圾回收。

   6.程序由表达式构成。

Lisp程序是表达式树,每个表达式返回一个值。这跟Fortran及其后续语言截然不同,这些语言区分表达式和语句。

      Fortran I这样区分很自然,因为它没有嵌套语句。所以虽然需要表达式来做数学计算,非表达式结构就没有必要返回任何值了,因为没有办法使用它。

      这个限制随着块结构语言的出现而消失了,但是到这个时候为时已晚。表达式和语句之间的划分已经不可改变了。它从Fortran扩散到Algol,又散布到其后续语言。

   7.符号类型。

符号实际上就是指向存储在hash表里的字符串的指针。这样你可以通过比较指针来判断是否相等,不需要比较每一个字符。

   8.代码中使用符号和常量组成的树的表示法。

   9.任何时刻都可以使用整个语言。

它没有真正地区分读时刻、编译时刻和运行时刻。你可以在读的时候编译或者运行代码,编译时读或者运行代码;以及运行时刻读或者编译代码。

      读时刻运行代码让用户可以修改Lisp的语法;编译时刻运行代码是宏(macro)的基础;运行时刻编译是使得Lisp可以作为象 Emacs这样的程序的扩展语言的基础;运行时刻读使得程序可以使用s-expression进行通讯,这一思想最近以XML的方式被人们重新发现了。

Lisp刚出现的时候这些思想不可能在普通编程实践中出现,因为当时的编程实践是由50年代的硬件条件所决定的。随着时间的发展,一代代的主流流行语言逐步向Lisp演化。思想1~5现在已经很普遍了。思想6正开始在主流语言中出现。Python以某种形式包含了第7点,尽管还没有明确的语法。

第8点可能是最有意思的一点。思想8和9能够成为Lisp的一部分完全出于偶然,因为Steve Russell实现了一个McCarthy没有预期被实现的东西。然而正是这两个思想导致了Lisp的奇怪的外表和最与众不同的特征。Lisp看起来很奇怪主要不是因为它的语法很奇怪,而是因为它没有语法;其它语言要做语法分析,而Lisp程序被直接表达成为语法树,这些树在后台构造,它由Lisp的数据结构—列表—组成。

用语言自身的数据结构来表达语言实际上是一个强大的特性。思想8和9合起来意味着你可以写一个可以写程序的程序。听起来这可能有点奇怪,但是在Lisp里面这是常见的事情。最普遍的做法就是使用宏。

“宏”在Lisp里面跟在其它语言里面不是一个意思。Lisp的宏可以象缩写这么简单,也可以象编译器这么复杂。如果你想真正理解Lisp,或者拓展你的语言视野,你应该更多地学习宏。

据我所知,(Lisp意义上的)宏只在Lisp里面出现。这部分因为为了实现宏,你可能要把语言弄得看起来象Lisp一样奇怪。这也可能因为如果你加入了这个终极威力,你就不能说你发明了一个新的语言,只能说是一个新的Lisp变体。

我这样说主要是开玩笑,不过确实是这样。如果你一定一个语言有car、cdr、cons、quote、cond、atom、eq和把函数表示为列表的表示方法,那么你就可以用这些来构造Lisp的所有其它部分。这实际上是Lisp的根本性质:McCarthy把Lisp做成这个样子就是出于这个目的。
语言的作用

那么假定Lisp确实在某种程度上代表了主流语言逐步接近的一种极限—这样是否意味着你就应该用它来写软件呢?使用一个不强大的语言有什么损失?有时候不用最先进的创新技术不是更明智的决定吗?语言的普及度本身不也能说明点什么吗?酷头发的老板希望使用比较容易招到程序员的语言难道不对吗?

当然有些项目中编程语言的选择无关紧要。原则上,应用的要求越高,使用强大的语言的好处就越多。但是大量的项目的要求一点都不高。多数编程工作就是写一些小的粘合程序,对于这种小程序,你可以使用任何熟悉的,有可以满足需要的库的语言。如果你要把一个Windows应用的数据传给另一个,当然用Visual Basic。

你也可以用Lisp写小的集成程序(我用它当桌面计算器),但是Lisp这样的语言真正发挥作用是在另一种极端情况中:你要写复杂程序来解决难题,并面临严酷的竞争。一个很好的例子是航班费用搜索问题,它是ITA Software为Orbitz开发的软件。他们进入的是一个已经被两个大的不易撼动的竞争对手Travelocity和Expedia所统治的市场,然而他们却在技术上羞辱了对手。

ITA的应用的核心是200,000行Common Lisp程序,它能搜索的可能性比竞争对手要多出多个数量级,它的竞争对手还在用主机时代的编程技术。(尽管ITA在某种意义上也在使用主机时代的编程语言。)我从来没有看过ITA的代码,不过它的一个顶尖的开发人员说他们使用了很多宏,这一点都不奇怪。
向心力

我并不是说使用不常用的技术没有代价。酷头发的老板担心这点并不是完全没有道理。但由于不理解风险,他倾向于夸大它们。

我可以想象使用不常用的语言可能会出现下面三个问题。你的程序可能和用其它语言开发的程序一起工作起来可能有点问题。你能用的库可能比较少。还有招聘程序员可能有点困难。

这些问题有多严重呢?第一个问题的重要性取决于你对整个系统有没有控制。如果你写的软件要能够在远程用户的不开放的烂操作系统(我可没说名字)上运行,用操作系统自己的语言来开发你的应用可能有点优势。但是如果你控制整个系统,而且有所有部分的源码,我猜ITA就是这样的情况,你就可以选择任何语言。如果出现了不兼容的情况,你可以自己解决。

在服务器端应用中,你很幸运可以使用最先进的技术,我想这就是Jonathan Erickson所描述的“编程语言复兴”的主要动机。这也是为什么我们能够听到Perl和Python这样的新语言的原因。我们不是因为人们用它们开发Windows应用才听到这些语言的,而是因为人们在服务器上使用它们。鉴于软件正在从桌面迁移到服务器上(这一点连微软都放弃了抵抗),将来不得不使用半吊子技术的压力将越来越小。

至于库,它的重要性同样取决于应用。对于要求不高的应用,库的可用性可能比语言本身的威力更重要。哪里是平衡点呢?不好说,不过不管具体位置在哪里,都应该没有达到严肃意义上的应用这个程度。如果一个公司自认为身处软件行业,而他们正在写的软件要成为公司的产品的话,那么它差不多要用若干个黑客花半年时间来开发。在这种规模的项目中语言的威力可能要比方便的已经存在的库的作用重要。

酷头发的老板担心的第三个问题,招聘程序员的难度,我认为似是而非。话说回来,你到底要招多少个黑客呢?现在大家公认软件最好由少于十个人的团队开发。这种规模的黑客的招聘对于任何有人听说过的语言都不是件难事。如果你找不到十个Lisp黑客,那么你的公司可能没有处在适合开发软件的城市。

实际上,选择更强大的语言可能缩小所需要的团队的规模,这是因为:(1)使用更强大的语言后你可能不需要那么多黑客了;(2)用更先进的语言的黑客往往更聪明。

我不是说你就不会受到要求使用被认为是“标准”技术的压力了。在Viaweb(现在的Yahoo Store)的时候,一些风险投资和潜在的买家对我们使用Lisp感到意外。不过他们对于我们的其它做法也不理解,包括使用大众化的Intel机器而不是 Sun这样的“产业级”的机器作为服务器;使用开源的Unix变体FreeBSD而不是Windows NT这样的商业操作系统;忽略当时被认为是电子商务标准的SET(现在都没人记得了)等等。

你不能让穿西装的为你做技术决定。我们使用Lisp有没有让一些潜在的买家惊慌呢?嗯,有一点,但是如果我们不使用Lisp,我们就无法写出他们想买的软件。看起来怪异的事情实际上是起因。

如果你创办一个创业公司,不要为了取悦于风险投资或者潜在的买家而设计软件。为了取悦用户而设计软件。如果你赢得了用户,其它的东西都会随之而来。如果你不能赢得用户,没有人会关心你的技术选择是多么令人欣慰地正统。
平庸的代价

使用不太强大的语言会有多少损失?实际上关于这个问题有些数据可以看。

威力的最方便的度量方式可能是代码规模。高级语言的最重要的特点是更大的抽象—更大的砖块,所以你用更少的砖块就可以造出同样大小的墙。所以语言越强大,程序就越短(当然不是简单指字符数,而是指独立元素)。

强大的语言是如何让你写出更短的程序的呢?如果语言允许的话,一项可以采用的技术是自底向上编程。不要直接用基本语言开发你的应用,而是在基本语言基础上构造一个适用于你的特定的应用的语言,然后用那个语言来写你的程序。这样写出的代码的总和可能比直接用基本语言写整个应用要少很多—实际上,大多数压缩算法就是这样工作的。自底向上的程序也比较容易修改,因为很多情况下语言层根本不需要变动。

代码规模很重要,因为写程序所需要的时间主要取决于其长度。如果用另一个语言会使程序长度是现在的三倍的话,它往往也需要三倍长的时间来写—你不能通过招聘更多的人来解决这个问题,因为到达一定规模之后增加新的人手完全是浪费。Fred Brooks在他的著名的《人月神话》一书里描述了这个现象,而且到目前为止我所看到的一切都证实了他的说法。

那么用Lisp可以使程序变短多少呢?比方说,我所听到的关于Lisp和C的比率大约是7~10倍。但是最近《New Architect》杂志上一篇关于ITA的文章说“一行Lisp可以取代20行C”,鉴于这篇文章有大量对ITA总裁的引用,我猜他们是从ITA得到这个数字的。如果这样的话我们应该可以相信它:ITA的软件除了Lisp之外也用到了很多C和C++,所以他们是以经验为依据的。

我猜想这些倍数不是一成不变的。我觉得当你碰到的问题越难,或者你的程序员越聪明时倍数还会增加。好的黑客可以把工具用得更好。

至少最为一个数据点来看,如果你想跟ITA竞争,而又选择用C来写你的软件的话,那么他们的开发速度就比你快二十倍。如果你花一年时间开发出某个功能,他们可能在三个星期内就能复制出来。相反地,如果他们花了三个月时间开发出新东西,你要花5年才能赶上。

你知道吗?这还是最好情况。当我们在讨论代码规模比例的时候,我们简单地假定用相对弱的语言也能够把程序写出来。但实际上程序员能做的事情是有限的。如果你试图使用一个过于低级的语言来解决一个难题,你会碰到一个临界点,脑子里面同时要处理太多的东西了。

所以我刚才说ITA花三个月开发的东西竞争对手要花五年才能复制的时候,我是说如果不出什么差错的情况下要五年。实际上,从大多数公司的经验来看,任何要花5年时间的开发项目可能都根本无法完成。

我承认这是个极端的例子。ITA的黑客们出奇地聪明,而且C是一个相当低级的语言。但是在高度竞争的市场中,两三倍的差距都足以让你永远无法赶上竞争对手。
处方

对于这种可能性酷头发的老板可能连想都不愿意想。而且他们大多数确实不去想。因为,如果真的发展到那个情况,酷头发的老板不在乎他的公司是否被挤压得很厉害,只要没有人能证明是他的错。对他们个人来说最安全的方案就是随大流。

在大规模组织里,用来描述这个方法的词汇叫“行业最佳实践”。它的目的就是保护酷头发的老板不必承担责任:如果他选择的是“行业最佳实践”,而公司失败了,他不应该受到指责。那不是他的选择,是行业的选择。

我相信这个术语最初是用于描述会计方法之类的东西。它的意思大概来说就是不要做出格的事情。在会计上这可能是个好主意。“最新式的”和“会计”这两个词跑到一起总不象是好事。但如果你把这个标准运用到技术决策上就错了。

技术通常就应该用最新式的。如同Erann Gat所指出来的[5],在编程语言方面“行业最佳实践”给你带来的不是最佳结果,而只是平均结果。如果你的决定导致你只能以比激进的竞争对手慢的多的速度开发软件,你就是被“最佳实践”误导了。

因此现在有两条我认为非常有价值的信息。实际上我是根据自己的亲身经历体会到的。第一条,语言的威力各不相同。第二条,多数经理故意忽略这一点。这两点事实实际上就是赚钱的处方。ITA就是这个处方的活生生的例子。如果你想在软件行业赢,选定一个能找到的最难的问题,选择一个能够得到的最强大的语言,然后就等着你的竞争对手的酷头发的老板们回归平庸吧。
附录:威力

作为一个所提到的关于语言相对威力的示例,让我们来考虑下面这个问题。我们来写一个函数产生累加器—也就是一个函数接受一个数字n,然后返回一个函数,该函数接受另一个数字i,并返回n和i累加后的结果。

(说的是累加,不是加法。累加器要进行累加。)

用Common Lisp可以这样实现

(defun foo (n)
  (lambda (i) (incf n i)))


用Perl 5,

sub foo {
  my ($n) = @_;
  sub {$n += shift}
}


它需要的元素比Lisp多,这是因为在Perl中必须手工抽取参数。

Smalltalk的代码比Lisp略微长一点

foo: n
  |s|
  s := n.
  ^[:i| s := s+i. ]


因为虽然一般情况下词法变量(lexical variable)可以解决这个问题,但是你不能为一个参数赋值,所以你还是要创建一个新的变量s。

Javascript实现又长了一点,由于Javascript区分语句和表达式,你需要显示的return语句来返回值:

function foo(n) {
  return function (i) {
    return n += i
  }
}


(公平地说,Perl也做这样的区分,但是典型的Perl风格是省略return。)

如果你试图把上述Lisp/Perl/Smalltalk/Javascript代码翻译到Python,你就会碰到一些限制。由于 Python不完全支持词法变量,你必须创建一个数据结构来保存n的值。而且尽管Python确实支持函数类型,它没有文字表示(除非函数体是单个表达式),所以你需要创建一个命名函数来返回。结果就是这样:

def foo(n):
  s = [n]
  def bar(i):
    s[0] += i
    return s[0]
  return bar


Python使用者可能合理地问为什么不能简单地写成

def foo(n):
  return lambda i: return n += i


或者甚至

def foo(n):
  lambda i: n += i


我猜有天他们可能可以。(但如果他们不想等到Python走完向Lisp的演化道路,他们可以……)

在面向对象的语言中,你可以通过定义一个类,类中包含一个方法并且为每个外部作用域中的变量定义一个域,来有限度地模拟closure(一个函数,它可以引用包含它的外部作用域中定义的变量)。这要求程序员自己来做那些全面支持词法作用域的语言的编译器所做的代码分析,而且当多个函数引用同一个变量时这种方式就不行了,不过它已经足够处理我们这个简单的问题了。

Python专家们似乎认为这是用Python解决这个问题的比较好的方式,可以写成

def foo(n):
  class acc:
    def __init__(self, s):
        self.s = s
    def inc(self, i):
        self.s += i
        return self.s
  return acc(n).inc


或者

class foo:
  def __init__(self, n):
      self.n = n
  def __call__(self, i):
      self.n += i
      return self.n


我列出这些因为我不希望Python鼓吹者们说我错误地表达了该语言,不过我觉得这两者方式似乎都比第一个版本更复杂。它们做的事情是一样的,都是用一个单独的地方保存累加器,区别不过在于存在一个对象的域里面还是列表的头里面。而且使用特殊的,保留的域名称,特别是__call__,看起来有点拼凑。

在Perl和Python的对抗中,Python黑客们的论点似乎是Python比Perl优雅,但是这个例子显示出威力才是终极优雅:Perl程序更简单(包含更少的元素),尽管其语法难看一点。

其它语言怎么样呢?对于本文中提及的其它语言—Fortran、C、C++、Java和Visual Basic—不是很清楚它们能不能解决这个问题。Ken Anderson说下面的代码差不多是你用Java能够得到的最接近的答案了:

public interface Inttoint {
  public int call(int i);
}

public static Inttoint foo(final int n) {
  return new Inttoint() {
    int s = n;
    public int call(int i) {
      s = s + i;
      return s;
    }
  };
}


这个没有完全达到要求,因为它只处理整数。跟Java黑客们通过一堆电子邮件后,我只能说为上述代码写一个多态的版本要么极端笨拙,要么不可能。如果有人愿意写一个我倒很乐意看看,不过我自己是没有耐心了。

当然,不是说你用其它语言就完全不能解决这个问题。理论上所有的这些语言都是图灵等价的,也就意味着严格来说你可以用任何语言写任何程序。怎么样才能做到这一点呢?对于限制这种情况,用不强大的语言写一个Lisp解释器。

听起来像个笑话,但是这在大规模的编程项目中不同程度地多次出现,以至于人们为这种现象起了个名字,Greenspun的第十个规则:

任何足够复杂的C或者Fortran程序都包含一个临时拼凑的,非正式规范的,充满错误的,低效的代码来实现半数的Common Lisp功能。

如果你要解决一个难题,问题不在于是否选择足够强大的语言,而是选择(1)使用强大的语言;(2)写一个事实上的解释器;(3)把你自己当作人工解释器。我们在Python的例子中已经看见这种情况出现了,在那里面我们实际上就是模拟编译器实现词法变量时生成的代码。

这样的实践不但常见,而且已经约定俗成了。例如,在面向对象世界里你总是能听到“模式”[6]。我怀疑这些模式有时候就是上述第(3)种情况,人工编译器在工作,的迹象。当我在我的程序里面看到模式时,我把它当成是问题的征兆。程序应该只映射它要解决的问题。代码中任何其它规律性的东西都是一种所使用的抽象不够强大的征兆,至少对我来说是这样—我在手工生成扩展,我应该写宏来做这样的扩展。


   1.IBM 704的CPU跟冰箱差不多大,但要重很多。CPU重3150磅,4K内存在另一个箱子里,重4000磅。最大的家用冰箱之一,Sub-Zero 690,重656磅。
   2.Steve Russell在1962年还写了第一个(数字)计算机游戏Spacewar。
   3.如果你想骗酷头发的老板让你用Lisp写软件,你可以试试告诉他那是XML。
   4.用其它Lisp方言实现累加器生成器:

      Scheme: (define (foo n) (lambda (i) (set! n (+ n i)) n))
      Goo:    (df foo (n) (op incf n_)))
      Arc:    (def foo (n) [++ n -])


   5.Erann Gat在JPL的关于“行业最佳实践”的悲伤的故事启发了我批判这个被广泛滥用的短语。
   6.Peter Norvig发现《设计模式》中的23个模式中的16个在Lisp中“不存在或者更简单”。
   7.感谢很多人回答我的关于不同语言的问题以及阅读本文草稿,他们是Ken Anderson,Trevor Blackwell,Erann Gat,Dan Giffin,Sarah Harlin,Jeremy Hylton,Robert Morris,Peter Norvig,Guy Steele和Anton van Straaten。它们不应因这里表达的任何观点而受谴责。

其它:

很多人对本文作了回应,所以我做了另一个页面来处理他们提出的问题:Re: Revenge of the Nerds。

本文在LL1邮件列表中引起了广泛而有用的讨论,特别是Anton van Straaten关于语义压缩的邮件。

LL1上的一些邮件激发我在Succinctness is Power一文中对语言的威力的主题进行了更深入的探讨。

累加器生成器基准页面上包含了大量的关于这个问题的规范实现。[url][/url]

你可能感兴趣的:(lisp 编程)