Loading...

书呆子的复仇

Original

2002年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++更好,什么时候不如C++?还有其他语言在某些情况下比这两者都更好吗?

一旦你开始考虑这个问题,你就打开了一个真正的潘多拉魔盒。如果尖头老板不得不考虑这个问题的全部复杂性,他的大脑就会爆炸。只要他认为所有语言都是等价的,他所要做的就是选择那个似乎最有势头的语言,因为这更多是一个时尚问题而不是技术问题,即使对他来说也很容易得出正确的答案。

但是,如果语言不尽相同,他突然就必须解决两个同时存在的方程式,试图找到一个最佳平衡点,在这个平衡点上,他对两件他一无所知的事情达成平衡:解决他需要解决的问题最适合的20多种主要编程语言的相对适用性,以及为每种语言找到程序员、库等的可能性。 如果这就是开启这扇大门后的结果,难怪尖头老板不想打开它。

相信所有编程语言都是等价的这个弊端就是,这不是真的。但它的优点是,这样可以让生活简单很多。

我想这就是这个想法如此广泛流传的主要原因。这是一个舒适的想法。

我们知道Java一定很不错,因为它是最新的编程语言。或者不是?如果你从远处观察编程语言的世界,它看起来就像Java是最新的东西。(从足够远的地方看,你只能看到太阳公司支付的大型闪烁的广告牌。) 但是,如果你近距离观察这个世界,你会发现,"酷"程度也是有等级的。在黑客子文化中,有另一种语言叫Perl,被认为比Java酷得多。 例如,Slashdot就是用Perl生成的。我想你不会在那里找到Java Server Pages的用户。但是,还有一种更新的语言叫Python,它的用户往往看不起Perl,更多正在等待着。

如果你按顺序看这些语言,Java、Perl、Python,你会注意到一个有趣的模式。至少,如果你是一个Lisp黑客,你会注意到这个模式。每一个语言都更像Lisp。Python甚至复制了一些许多Lisp黑客认为是错误的特性。你可以逐行把简单的Lisp程序翻译成Python。这是2002年,编程语言几乎赶上了1958年的水平。

追上数学

我的意思是,Lisp最初是由约翰·麦卡锡在1958年发现的,而现在普及的编程语言才刚刚赶上他当时开发的思路。

现在,这怎么可能呢?计算机技术不是应该变化很快吗?我的意思是,1958年,计算机还是冰箱大小的庞然大物,处理能力相当于一只手表。一种那么古老的技术怎么会还有相关性,更别说优于最新的发展了?

我来告诉你原因。这是因为Lisp实际上并不是被设计用作编程语言,至少在我们今天所理解的意义上不是。我们所理解的编程语言是用来告诉计算机做什么的东西。麦卡锡最终确实打算开发一种编程语言,但我们最终得到的Lisp是建立在他做的一项理论练习之上的——试图定义一种比图灵机更方便的替代品。正如麦卡锡后来所说的,

另一种证明Lisp比图灵机整洁的方法是编写一个通用的Lisp函数,并证明它比描述通用图灵机更简洁和更易理解。这就是Lisp函数eval,它计算Lisp表达式的值....编写eval需要发明一种表示Lisp函数作为Lisp数据的符号,而这种符号是为了这篇论文的目的而制定的,并没有想到会用它来实际表达Lisp程序。

接下来发生的是,在1958年底左右,麦卡锡的一个研究生史蒂夫·罗素看到了这个eval的定义,意识到如果他把它翻译成机器语言,结果就会是一个Lisp解释器。

这在当时是一个很大的惊喜。以下是麦卡锡在一次采访中后来说的:

史蒂夫·罗素说,为什么我不编程实现这个eval......,我对他说,哦,哦,你把理论和实践混为一谈了,这个eval是用来阅读的,而不是用来计算的。但是他还是继续做了。也就是说,他把我论文中的eval编译成了[IBM] 704机器代码,并修复了一些错误,然后把它作为一个Lisp解释器进行了广告宣传,这肯定也就是Lisp了....

突然之间,在几个星期内,麦卡锡发现他的理论练习变成了一种实际的编程语言——而且比他原本打算的要强大得多。

所以这种20世纪50年代的语言没有过时的简短解释就是,它不是技术,而是数学,数学是不会过时的。正确的比较对象不是50年代的硬件,而是比如1960年发现的快速排序算法,它至今仍然是最快的通用排序算法。

还有一种来自50年代的存活至今的语言是Fortran,它代表了一种完全不同的语言设计方法。Lisp是一种意外地被转化为编程语言的理论成果,而Fortran则是有意识地被开发为一种编程语言,但我们现在认为它是一种非常低级的语言。

Fortran I,也就是1956年开发的那种语言,与现代Fortran大不相同。Fortran I基本上就是带有数学运算的汇编语言。在某些方面它甚至比更新的汇编语言还要弱一些;比如,它没有子程序,只有跳转。而现代Fortran现在在某种程度上已经更接近Lisp,而不是Fortran I了。

Lisp和Fortran是两棵不同进化树的树干,一棵植根于数学,另一棵植根于机器架构。这两棵树从那时起就一直在逐渐收敛。Lisp一开始就很强大,在接下来的20年里变得越来越快。所谓的主流语言起初很快,在接下来的40年里逐步变得更强大,现在最先进的语言已经相当接近Lisp了。接近,但仍然缺少一些东西....

Lisp的不同之处

当Lisp最初被开发出来时,它体现了九个新的想法。其中有一些我们现在已经习以为常,另一些只出现在更高级的语言中,还有两个是Lisp独有的。这九个想法按照它们被主流采用的顺序排列如下:

条件语句。条件语句是一种if-then-else结构。我们现在已经习以为常了,但Fortran I没有这个功能。它只有一个基于底层机器指令的条件跳转。

函数类型。在Lisp中,函数是一种与整数或字符串等一样的数据类型。它们有字面表示,可以存储在变量中,可以作为参数传递等等。

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

动态类型。在Lisp中,所有变量都是指针。值有类型,而不是变量。给变量赋值或绑定就意味着复制指针,而不是它们指向的内容。

垃圾回收。

由表达式组成的程序。Lisp程序是由返回值的表达式组成的树。这与Fortran和大多数后续语言不同,后者区分表达式和语句。

在Fortran I中保持这种区分是很自然的,因为你不能嵌套语句。因此,虽然你需要表达式来进行数学运算,但没有必要让其他任何东西返回值,因为没有什么地方在等着接收它。

随着块结构语言的出现,这种限制消失了,但那时为时已晚。表达式和语句的区分已经根深蒂固了。它从Fortran传播到Algol,然后又传播到两者的后代。

符号类型。符号实际上是指向存储在哈希表中的字符串的指针。所以你可以通过比较指针来测试相等性,而不是逐个比较每个字符。

用树状符号和常量表示代码的符号。

整个语言随时都在那里。在读取时、编译时和运行时之间没有明确的区别。你可以在读取时编译或运行代码,在编译时读取或运行代码,在运行时读取或编译代码。

在读取时运行代码让用户可以重新编程Lisp的语法;在编译时运行代码是宏的基础;在运行时编译是Lisp作为扩展语言在程序(如Emacs)中使用的基础;在运行时读取使程序可以使用s表达式进行通信,这个想法最近被重新发明为XML。

当Lisp第一次出现时,这些想法与普通的编程实践相距甚远,这主要是受到了20世纪50年代末期可用硬件的影响。随着时间的推移,体现在一系列流行语言中的默认语言,已经逐步向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(现在属于雅虎在线商店),我们在风险投资者和潜在收购者眼中引起了一些眉目,因为我们使用了Lisp。但是我们还因为使用廉价的Intel服务器而不是所谓"工业级"的Sun服务器,使用一个当时还鲜为人知的开源Unix变体FreeBSD而不是真正商业操作系统Windows NT,无视一个名为SET的所谓电子商务标准(现在谁都不记得它了)等而引起了关注。

你不能让套装们为你做技术决策。我们使用Lisp让一些潜在的收购者感到担忧吗?是的,稍微有一些,但如果我们没有使用Lisp,我们就无法编写使他们想要收购我们的软件。对他们来说看起来像一个怪异的地方,其实就是原因和结果。

如果你开始创业,不要设计你的产品来取悦风险投资者或潜在的收购者。*设计你的产品来取悦用户。*如果你赢得了用户,其他一切都会随之而来。而如果你没赢得用户,没有人会在乎你的技术选择有多令人放心。

平庸的代价

使用一种不太强大的语言究竟会失去多少?实际上,我们已经有一些关于这方面的数据。

最方便的功能衡量可能是代码大小。高级语言的意义在于提供给你更大的抽象--更大的"砖块",让你建一堵给定大小的墙时不需要那么多。所以语言越强大,程序就越短(当然不仅仅是字符数,而是不同元素的数量)。

一种更强大的语言是如何使你编写更短的程序的呢?如果语言允许,你可以使用一种叫做自底向上编程的技术。你不仅仅在基础语言上编写应用程序,而是在基础语言之上构建一种用于编写类似程序的语言,然后用它来编写你的程序。合并后的代码可能要比完全在基础语言中编写要短得多--事实上,这也是大多数压缩算法的工作原理。一个自底向上的程序也应该更容易修改,因为在许多情况下,语言层根本不需要改变。

代码大小很重要,因为编写程序所需的时间主要取决于程序的长度。如果你用另一种语言编写程序,长度会是原来的三倍,那么编写时间也会是原来的三倍--你无法通过雇佣更多人来解决这个问题,因为超过一定规模,新招聘的人反而会成为负担。福雷德·布鲁克斯在他著名的著作《人月神话》中描述了这种现象,我所看到的一切都证实了他的说法。

那么,如果用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}
}

这比Lisp版本有更多的元素,因为您必须手动提取参数。

在Smalltalk中,代码比Lisp稍长一些

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

因为尽管在一般情况下,词法变量是可用的,但您不能对参数进行赋值,所以必须创建一个新变量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,他们总是可以...)

在面向对象的语言中,可以在一定程度上模拟闭包(引用封闭作用域中定义的变量的函数)。只需定义一个有一个方法和一个字段的类来替换每个来自封闭作用域的变量。这使程序员进行编译器在拥有完全词法作用域支持的语言中会做的那种代码分析,如果有多个函数引用同一个变量,它就无法工作,但在像这样的简单情况下已经足够了。

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 解释器。

这听起来像个笑话,但在大型编程项目中经常会发生这种情况,以至于有了一个名称来描述这种现象,格林斯彭第十法则:

任何足够复杂的 C 或 Fortran 程序都包含一个非正式指定的、有缺陷的和缓慢的 Common Lisp 的约束性实现。

如果你试图解决一个很难的问题,问题不在于你是否使用了强大的语言,而是你是否 (a) 使用了强大的语言, (b) 编写了一个实际上的解释器,或 (c) 自己成为了一个人工编译器。我们已经可以在 Python 示例中看到这种情况的开始,在这里我们正在模拟编译器生成的代码来实现一个词法变量。

这种做法不仅很普遍,而且已经制度化了。例如,在面向对象的世界里,你会听到大量关于"设计模式"的讨论。我想知道这些模式是否有时是(c)情况的证据,即人工编译器在起作用。当我在自己的程序中看到这些模式时,我认为这是麻烦的征兆。程序的形状应该只反映它需要解决的问题。代码中的任何其他规律性都表明,我使用的抽象还不够强大--通常是我在手动生成某个宏的展开。

注释

IBM 704 CPU 差不多有一台冰箱那么大,但重得多。CPU 重 3150 磅,4K 内存存在另一个箱子里,重 4000 磅。Sub-Zero 690 是家用最大冰箱之一,重 656 磅。

Steve Russell 还编写了第一款(数字)计算机游戏 Spacewar,发布于 1962 年。

如果你想骗一个尖头皮头老板让他允许你用 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。他们对我表达的任何观点都不负责任。

相关内容:

很多人已经对这个演讲作出了回应,所以我建立了一个额外的页面来处理他们提出的问题:对"程序员复仇"的回应

它还引发了LL1邮件列表上广泛而且通常很有用的讨论。特别请参阅 Anton van Straaten 关于语义压缩的邮件。

LL1 上的一些邮件让我试图在简洁性就是力量中更深入地探讨语言力量的主题。

累加器生成器基准的一组标准实现收集在自己的页面上。

日文翻译,西班牙语翻译,中文翻译