书呆子的复仇
想要创业?获得Y Combinator的投资。
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++更好?是否存在某些情况下其他语言比两者都更好?
一旦你开始考虑这个问题,你就打开了一个真正的潘多拉盒子。如果尖头发的老板不得不以全部复杂性来思考这个问题,他的大脑会爆炸。只要他认为所有语言都等价,他所要做的就是选择一个似乎最有势头的语言,既然这更多是时尚问题而不是技术问题,即使他也可能得到正确的答案。但如果语言各不相同,他突然必须同时解两个方程,试图在他一无所知的两件事之间找到最佳平衡:解决他需要解决的问题的二十种左右主要语言的相对适用性,以及为每种语言找到程序员、库等的可能性。如果门后是这样的东西,尖头发的老板不想打开它也就不足为奇了。
认为所有编程语言都等价的缺点是这不是真的。但优点是它让你的生活简单得多。我认为这是这个想法如此广泛传播的主要原因。这是一个令人舒适的想法。
我们知道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是故意被开发为一种编程语言的,但我们现在认为是一种非常低级的语言。
1956年开发的Fortran I语言与现在的Fortran是完全不同的动物。Fortran I基本上是带有数学的汇编语言。在某些方面它比最近的汇编语言功能更弱;例如,没有子程序,只有分支。现在的Fortran可以说更接近Lisp而不是Fortran I。
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得到这个数字的。如果是这样,那么我们可以对此有一些信心;ITA的软件也包括很多C和C++以及Lisp,所以他们是从经验说话的。
我的猜测是这些倍数甚至不是恒定的。我认为当你面对更难的问题时,以及当你有更聪明的程序员时,它们会增加。一个真正优秀的黑客可以从更好的工具中挤出更多。
无论如何,作为曲线上的一个数据点,如果你要与ITA竞争并选择用C编写你的软件,他们将能够比你快二十倍地开发软件。如果你在一个新功能上花费一年,他们将能够在不到三周内复制它。而如果他们只花三个月开发新东西,你将在五年后才有它。
你知道什么?那是最好的情况。当你谈论代码大小比率时,你隐含地假设你实际上可以在较弱的语言中编写程序。但事实上,程序员的能力是有限制的。如果你试图用太低级的语言解决一个难题,你会达到一个点,即一次要记住的东西太多。
所以当我说ITA的假想竞争对手需要五年时间复制ITA可以在Lisp中三个月内写出的东西时,我的意思是如果不出错需要五年。事实上,在大多数公司中事情运作的方式,任何需要五年的开发项目很可能根本无法完成。
我承认这是一个极端情况。ITA的黑客似乎异常聪明,而C是相当低级的语言。但在竞争市场中,即使是二比一或三比一的差异也足以保证你永远落后。
一个配方
这是尖头发的老板甚至不想考虑的那种可能性。所以他们大多数人不会。因为,你知道,归根结底,尖头发的老板不介意他的公司被踢屁股,只要没有人能证明是他的错。对他个人来说,最安全的计划是紧贴群体的中心。
在大型组织内部,用来描述这种方法的短语是"行业最佳实践"。其目的是保护尖头发的老板免于责任:如果他选择的是"行业最佳实践"的东西,公司亏损,他不能被责怪。他没有选择,行业选择了。
我相信这个术语最初是用来描述会计方法等的。它的意思大致是,不要做任何奇怪的事情。在会计中,这可能是个好主意。“尖端"和"会计"这两个词听起来不太合适。但当你将这个标准导入技术决策时,你开始得到错误的答案。
技术应该是尖端的。在编程语言中,正如Erann Gat所指出的,“行业最佳实践"实际给你的不是最好的,而只是普通的。当一个决定导致你以更积极竞争对手的一小部分速度开发软件时,“最佳实践"是用词不当。
所以我们有两条我认为非常有价值的信息。事实上,我从自己的经验中知道这一点。数字1,语言的力量各不相同。数字2,大多数经理故意忽略这一点。这两者之间,这两个事实字面上就是一个赚钱的配方。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. ]
因为虽然一般来说词法变量工作,但你不能对参数进行赋值,所以你必须创建一个新变量s。
在Javascript中,示例再次稍长,因为Javascript保留了语句和表达式之间的区别,所以你需要显式的return语句来返回值:
function foo(n) {
return function (i) {
return n += i
}
}
(公平地说,Perl也保留了这种区别,但以典型的Perl方式通过让你省略returns来处理它。)
如果你尝试将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的其余部分,他们总是可以…)
在OO语言中,你可以在有限程度上模拟闭包(一个引用在封闭作用域中定义的变量的函数),通过定义一个类,该类有一个方法和一个字段来替换封闭作用域中的每个变量。这使得程序员做一些在有完全词法作用域支持的语言中由编译器做的代码分析工作,如果多个函数引用同一个变量,它就不起作用,但在像这样的简单情况下足够了。
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__
,似乎有点像hack。
在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一半的临时非正式指定的充满bug的缓慢实现。
如果你试图解决一个难题,问题不是你是否会使用足够强大的语言,而是你是否会(a)使用强大的语言,(b)为一种语言编写事实上的解释器,或(c)你自己成为一种语言的编译器。我们在Python示例中已经看到开始发生这种情况,我们实际上在模拟编译器为实现词法变量而生成的代码。
这种做法不仅常见,而且制度化。例如,在OO世界中,你听到很多关于"模式"的东西。我想知道这些模式有时是否不是案例(c)的证据,即人类编译器在工作。当我在程序中看到模式时,我认为这是一个麻烦的迹象。程序的形状应该只反映它需要解决的问题。代码中的任何其他规律性,对我来说至少,表明你使用的抽象不够强大——通常是你手工生成一些你需要编写的宏的扩展。
注释
[1] IBM 704 CPU大约有冰箱那么大,但重得多。CPU重3150磅,4K RAM在另一个重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中"不可见或更简单”。
感谢许多回答了我关于各种语言的问题和/或阅读了本文草稿的人,包括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中更深入地探讨语言力量主题。
累加器基准测试的规范实现集收集在它们自己的页面上。
日语翻译,西班牙语翻译,中文翻译
你会在《黑客与画家》中找到这篇文章和其他14篇。