书呆子的复仇
Original2002年5月
“我们是冲着C++程序员去的。我们设法把很多人拖到Lisp的半路上。”
- Guy Steele,Java规范的共同作者
在软件行业中,尖头学者和另一股同样强大的力量——尖头老板之间存在着持续的斗争。每个人都知道尖头老板是谁,对吧?我认为科技界的大多数人不仅认识这个卡通角色,还知道在他们公司中以他为模型的真实人物。
尖头老板奇迹般地结合了两种通常单独存在但很少同时出现的特质: (a) 他对技术一无所知,和 (b) 他对此有非常强烈的看法。
例如,假设你需要编写一段软件。尖头老板完全不知道这段软件应该如何工作,无法区分一种编程语言与另一种编程语言,但他知道你应该用什么语言来编写它。没错。他认为你应该用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年,计算机是冰箱大小的庞然大物,处理能力相当于手表。任何如此古老的技术怎么可能仍然相关,更不用说优于最新的发展了?
我告诉你原因。因为Lisp并不是为了成为一种编程语言而设计的,至少不是我们今天所说的那种。我们所说的编程语言是用来告诉计算机该做什么的。McCarthy最终确实打算开发一种这种意义上的编程语言,但我们最终得到的Lisp是基于他作为理论练习所做的某些事情——一种定义更方便的替代图灵机的努力。正如McCarthy后来所说,
另一种表明Lisp比图灵机更整洁的方法是编写一个通用Lisp函数,并表明它比通用图灵机的描述更简洁易懂。这就是Lisp函数eval...,它计算Lisp表达式的值....编写eval需要发明一种表示Lisp函数作为Lisp数据的符号,而这种符号是为了论文的目的而设计的,并没有考虑到它会在实践中用于表达Lisp程序。
接下来发生的事情是,在1958年末,Steve Russell,McCarthy的一个研究生,看到了这个eval的定义,并意识到如果他将其翻译成机器语言,结果将是一个Lisp解释器。
这在当时是一个大惊喜。以下是McCarthy后来在一次采访中对此的看法:
Steve Russell说,听着,为什么我不编写这个eval...,我对他说,哈哈,你把理论和实践搞混了,这个eval是为了阅读,而不是为了计算。但他还是继续做了。也就是说,他将我论文中的eval编译成[IBM] 704机器代码,修复了错误,然后将其宣传为Lisp解释器,这确实是。所以在那时,Lisp基本上就形成了今天的样子....
突然之间,我认为在几周内,McCarthy发现他的理论练习转变为一种实际的编程语言——而且是一种比他原本打算的更强大的语言。
所以,简短的解释为什么这个1950年代的语言并不过时是因为它不是技术而是数学,而数学不会过时。比较Lisp的正确对象不是1950年代的硬件,而是,比如说,1960年发现的快速排序算法,它仍然是最快的通用排序算法。
还有另一种语言仍然存活于1950年代,Fortran,它代表了与语言设计相反的方法。Lisp是一个理论的产物,意外地变成了一种编程语言。Fortran是故意作为一种编程语言开发的,但我们现在认为它是一种非常低级的语言。
Fortran I,在1956年开发的语言,与现代Fortran截然不同。Fortran I几乎是带有数学的汇编语言。在某些方面,它比更新的汇编语言更不强大;例如,没有子程序,只有分支。现代Fortran现在可以说比Fortran I更接近Lisp。
Lisp和Fortran是两棵不同进化树的主干,一棵扎根于数学,另一棵扎根于机器架构。这两棵树自那时起就一直在趋同。Lisp一开始就很强大,在接下来的二十年里变得快速。所谓的主流语言一开始是快速的,在接下来的四十年里逐渐变得更强大,直到现在它们中最先进的语言与Lisp相当接近。接近,但它们仍然缺少一些东西....
Lisp的不同之处
在最初开发时,Lisp体现了九个新思想。其中一些我们现在视为理所当然,另一些仅在更高级的语言中可见,还有两个仍然是Lisp独有的。这九个思想按主流采用的顺序如下:
条件语句。条件语句是一个if-then-else结构。我们现在视为理所当然,但Fortran I没有它。它只有一个基于底层机器指令的条件goto。
函数类型。在Lisp中,函数是一种数据类型,就像整数或字符串一样。它们有字面表示,可以存储在变量中,可以作为参数传递,等等。
递归。Lisp是第一个支持递归的编程语言。
动态类型。在Lisp中,所有变量实际上都是指针。值才有类型,而不是变量,赋值或绑定变量意味着复制指针,而不是它们指向的内容。
垃圾回收。
由表达式组成的程序。Lisp程序是表达式的树,每个表达式返回一个值。这与Fortran和大多数后续语言形成对比,后者区分表达式和语句。
在Fortran I中有这种区分是自然的,因为你不能嵌套语句。因此,虽然你需要表达式来进行数学运算,但没有必要让其他任何东西返回一个值,因为没有任何东西在等待它。
这种限制随着块结构语言的出现而消失,但到那时已经太晚了。表达式和语句之间的区分已经根深蒂固。它从Fortran传播到Algol,然后到它们的后代。
符号类型。符号实际上是指向存储在哈希表中的字符串的指针。因此,你可以通过比较指针来测试相等性,而不是比较每个字符。
使用符号和常量的树形结构的代码表示法。
整个语言始终存在。读取时间、编译时间和运行时间之间没有真正的区别。你可以在读取时编译或运行代码,在编译时读取或运行代码,以及在运行时读取或编译代码。
在读取时运行代码让用户重新编程Lisp的语法;在编译时运行代码是宏的基础;在运行时编译是Lisp在Emacs等程序中作为扩展语言使用的基础;而在运行时读取使程序能够使用s表达式进行通信,这一思想最近被重新发明为XML。
当Lisp首次出现时,这些思想与普通编程实践相去甚远,普通编程实践在很大程度上受限于1950年代末期可用的硬件。随着时间的推移,默认语言在一系列流行语言中逐渐演变为Lisp。思想1-5现在已经广泛传播。第6个开始出现在主流中。Python有第7个的某种形式,尽管似乎没有任何语法。
至于第8个,这可能是最有趣的。第8和第9个思想之所以成为Lisp的一部分,纯粹是偶然,因为Steve Russell实现了McCarthy从未打算实现的东西。然而,这些思想恰好是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应用程序的核心是一个20万行的Common Lisp程序,它搜索的可能性比他们的竞争对手多几个数量级,而竞争对手显然仍在使用主机时代的编程技术。(尽管ITA在某种意义上也在使用主机时代的编程语言。)我从未见过ITA的代码,但根据他们的一位顶级黑客的说法,他们使用了很多宏,我对此并不感到惊讶。
向心力
我并不是说使用不常见技术没有成本。尖头老板担心这一点并不是完全错误的。但因为他不理解风险,他往往会夸大它们。
我能想到三种使用不常见语言可能出现的问题。你的程序可能与用其他语言编写的程序不兼容。你可能拥有的库较少。你可能在雇佣程序员时遇到困难。
每个问题的严重性如何?第一个问题的重要性取决于你是否控制整个系统。如果你正在编写必须在远程用户的机器上运行的软件,而该机器上有一个有缺陷的封闭操作系统(我不提名字),那么用与操作系统相同的语言编写应用程序可能会有优势。但如果你控制整个系统并拥有所有部分的源代码,正如ITA可能所做的那样,你可以使用任何你想要的语言。如果出现任何不兼容,你可以自己修复。
在基于服务器的应用程序中,你可以使用最先进的技术,我认为这就是Jonathan Erickson所称的“编程语言复兴”的主要原因。这就是为什么我们甚至听说Perl和Python等新语言。我们之所以听说这些语言,并不是因为人们用它们编写Windows应用程序,而是因为人们在服务器上使用它们。随着软件逐渐离开桌面转向服务器(即使是微软似乎也对此心甘情愿),对使用中间技术的压力将越来越小。
至于库,它们的重要性也取决于应用程序。对于不那么复杂的问题,库的可用性可能会超过语言的内在力量。平衡点在哪里?很难确切说,但无论在哪里,它都远低于你可能称之为应用程序的任何东西。如果一家公司认为自己处于软件行业,并且他们正在编写将成为其产品之一的应用程序,那么这通常会涉及几个黑客,并且需要至少六个月的时间来编写。在这样一个规模的项目中,强大的语言可能开始超过现有库的便利性。
尖头老板的第三个担忧,即雇佣程序员的困难,我认为是一个红鲱鱼。毕竟,你需要雇佣多少黑客?我们现在都知道,软件最好由少于十人的团队开发。而且,对于任何人听说过的语言,你在这个规模上雇佣黑客应该没有问题。如果你找不到十个Lisp黑客,那么你的公司可能位于一个不适合开发软件的城市。
事实上,选择一种更强大的语言可能会减少你所需团队的规模,因为(a)如果你使用更强大的语言,你可能不需要那么多黑客,和(b)使用更高级语言的黑客往往更聪明。
我并不是说你不会受到使用被视为“标准”技术的压力。在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的软件包括大量的C和C++以及Lisp,因此他们是基于经验发言的。
我猜这些倍数甚至不是常数。我认为它们在面对更困难的问题时会增加,并且在你拥有更聪明的程序员时也会增加。一个真正优秀的黑客可以从更好的工具中榨取更多。
作为曲线上的一个数据点,无论如何,如果你与ITA竞争并选择用C编写软件,他们将能够以你的20倍速度开发软件。如果你花了一年时间开发一个新特性,他们将能够在不到三周的时间内复制它。而如果他们只花三个月开发一些新东西,你也将需要五年才能拥有它。
你知道吗?这还是最好的情况。当你谈论代码大小比率时,你隐含地假设你实际上可以用较弱的语言编写程序。但实际上,程序员的能力是有限的。如果你试图用一种过于低级的语言解决一个困难的问题,你会达到一个点,在这个点上你脑海中需要同时保持的信息太多。
所以当我说ITA的假想竞争对手需要五年才能复制ITA可以在三个月内用Lisp编写的东西时,我的意思是如果没有任何问题,五年。实际上,在大多数公司的运作方式中,任何需要五年的开发项目都可能根本无法完成。
我承认这是一个极端的例子。ITA的黑客似乎特别聪明,而C是一种相当低级的语言。但在竞争市场中,即使是两到三倍的差异也足以保证你总是落后。
一个食谱
这是尖头老板甚至不想考虑的可能性。因此,他们大多数人都不考虑。因为,你知道,当一切归结到实处时,尖头老板并不介意他的公司被打败,只要没有人能证明这是他的错。对他个人来说,最安全的计划是紧紧跟随群体的中心。
在大型组织中,用来描述这种方法的短语是“行业最佳实践”。其目的是保护尖头老板免于责任:如果他选择了某种“行业最佳实践”,而公司失败了,他就不能被指责。他没有选择,行业选择了。
我相信这个术语最初是用来描述会计方法等的。它的粗略意思是不要做任何奇怪的事情。在会计中,这可能是个好主意。“前沿”和“会计”这两个词听起来并不搭配。但当你将这一标准引入技术决策时,你开始得到错误的答案。
技术往往应该是前沿的。在编程语言中,正如Erann Gat所指出的,“行业最佳实践”实际上得到的不是最好的,而仅仅是平均水平。当一个决策导致你以比更激进的竞争对手慢得多的速度开发软件时,“最佳实践”就是一个误称。
所以在这里我们有两条我认为非常有价值的信息。实际上,我从自己的经验中知道这一点。第一,语言的力量各不相同。第二,大多数管理者故意忽视这一点。在这两者之间,这两个事实简直是赚钱的食谱。ITA就是这个食谱的一个例子。如果你想在软件行业获胜,只需接受你能找到的最困难的问题,使用你能获得的最强大的语言,然后等待你的竞争对手的尖头老板回归平均水平。
附录:力量
作为我所说的编程语言相对力量的一个例子,考虑以下问题。我们想编写一个生成累加器的函数——一个接受数字n并返回一个接受另一个数字i并返回n加上i的函数。
(这是加上,而不是加。累加器必须累加。)
在Common Lisp中,这将是
(defun foo (n)
(lambda (i) (incf n i)))
而在Perl 5中,
sub foo {
my ($n) = @_;
sub {$n += shift}
}
由于你必须在Perl中手动提取参数,因此它的元素比Lisp版本多。
在Smalltalk中,代码比Lisp稍长
foo: n
|s|
s := n.
^[:i| s := s+i. ]
因为虽然一般来说词法变量有效,但你不能对参数进行赋值,因此你必须创建一个新变量s。
在Javascript中,示例再次稍长,因为Javascript保留了语句和表达式之间的区别,因此你需要显式的返回语句来返回值:
function foo(n) {
return function (i) {
return n += i } }
(公平地说,Perl也保留了这种区别,但以典型的Perl方式处理,允许你省略返回。)
如果你尝试将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,他们总可以...)
在面向对象的语言中,你可以在有限的程度上通过定义一个具有一个方法和一个字段的类来模拟闭包(引用在封闭作用域中定义的变量的函数),以替代每个来自封闭作用域的变量。这使得程序员必须进行编译器在具有完全支持词法作用域的语言中所做的那种代码分析,如果多个函数引用同一个变量,它将不起作用,但在像这样的简单情况下是足够的。
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的一半。
如果你试图解决一个困难的问题,问题不是你是否会使用一种足够强大的语言,而是你是否会(a)使用一种强大的语言,(b)为一种语言编写一个事实上的解释器,或者(c)自己成为一种语言的人类编译器。我们已经看到这种情况在Python示例中开始发生,在那里我们实际上模拟了编译器生成的代码,以实现词法变量。
这种做法不仅常见,而且已经制度化。例如,在面向对象的世界中,你会听到很多关于“模式”的讨论。我想这些模式有时并不是案例(c),人类编译器在工作。当我在我的程序中看到模式时,我认为这是一个麻烦的迹象。程序的形状应该仅反映它需要解决的问题。代码中的任何其他规律对我来说都是一个迹象,表明我正在使用的抽象不够强大——通常是我正在手动生成我需要编写的某个宏的扩展。
注释
IBM 704 CPU的大小约为冰箱,但重得多。CPU重3150磅,4K的RAM在一个单独的箱子中,重4000磅。Sub-Zero 690是最大的家用冰箱之一,重656磅。
Steve Russell还在1962年编写了第一个(数字)计算机游戏Spacewar。
如果你想让尖头老板让你用Lisp编写软件,你可以试着告诉他这是XML。
以下是其他Lisp方言中的累加器生成器:
Scheme: (define (foo n)
(lambda (i) (set! n (+ n i)) n))
Goo: (df foo (n) (op incf n _)))
Arc: (def foo (n) [++ n _])
Erann Gat关于JPL的“行业最佳实践”的悲惨故事激励我去处理这个通常被误用的短语。
Peter Norvig发现设计模式中的23种模式中有16种在Lisp中是“不可见或更简单的”。
感谢许多回答我关于各种语言问题和/或阅读草稿的人,包括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中。
一组更大的标准实现的累加器生成基准测试被单独收集在一个页面上。