Featured image of post 极客的复仇

极客的复仇

探讨为什么1958年开发的Lisp语言至今仍是最强大的编程语言,以及为什么主流编程语言正在逐渐向Lisp靠拢。

📚 返回 Paul Graham 文章目录

极客的复仇

想创办一家创业公司?申请 Y Combinator 的资金支持。

2002年5月

“我们瞄准的是C++程序员。我们成功地把他们中的许多人拉到了Lisp的一半路程。”

  • Guy Steele,Java规范的合著者

(这是2002年5月在国际ICAD用户组会议上的主题演讲的扩展版本。它解释了为什么1958年开发的语言至今仍是最强大的,什么是编程语言的威力,什么时候需要这种威力,以及为什么尖头发老板(理想情况下,是你竞争对手的尖头发老板)会故意忽视这个问题。)

注意: 在这次演讲中,当我提到"Lisp"时,我指的是Lisp语言家族,包括Common Lisp、Scheme、Emacs Lisp、EuLisp、Goo、Arc等。

让我先承认我对ICAD了解不多。我知道它是用Lisp写的,实际上包含了Lisp,因为它允许用户创建和运行Lisp程序。

用Lisp写的程序包含Lisp是很常见的。Emacs是这样,Yahoo Store也是这样。但如果你仔细想想,这有点奇怪。有多少用C写的程序包含C,让用户在使用应用程序时实际运行C编译器?我想不出任何例子,除非你把Unix算作一个应用程序。演讲才开始一分钟,Lisp就已经显得很不寻常了。

现在,Lisp看起来不寻常对你们中的任何人来说可能都不是新闻。事实上,这可能是你们注意到的第一件事。

信不信由你,Lisp代码看起来如此奇怪是有原因的。Lisp看起来这样并不是因为它是由一群尖头学者设计的。它确实是由尖头学者设计的,但他们有务实的工程原因让语法看起来如此奇怪。

所有语言都是等价的吗? 在软件行业中,尖头学者和另一个同样强大的力量——尖头发老板之间一直存在着斗争。每个人都知道尖头发老板是谁,对吧?我想科技界的大多数人不仅认识这个卡通人物,而且知道他们公司中这个角色的原型是谁。

尖头发老板神奇地结合了两种单独很常见但很少一起出现的品质:(a) 他对技术一无所知,以及 (b) 他对技术有非常强烈的观点。

比如说,假设你需要写一个软件。尖头发老板完全不知道这个软件应该如何工作,也分不清一种编程语言和另一种,但他知道你应该用什么语言来写它。没错。他认为你应该用Java来写。

他为什么这么想?让我们看看尖头发老板的大脑里在想什么。他的想法大概是这样的:Java是一个标准。我知道它一定是,因为我在媒体上经常读到它。既然它是一个标准,使用它就不会惹上麻烦。这也意味着永远会有很多Java程序员,所以如果现在为我工作的程序员辞职了(不知为何,为我工作的程序员总是神秘地辞职),我可以很容易地找到替代者。

好吧,这听起来并不那么不合理。但它都基于一个未说出的假设,而这个假设实际上是错误的。尖头发老板相信所有编程语言基本上都是等价的。如果这是真的,他的想法就完全正确。如果语言都是等价的,当然,用其他人都在用的语言就好了。

但所有语言并不等价,我想我可以向你证明这一点,甚至不需要深入它们之间的差异。如果你在1992年问尖头发老板软件应该用什么语言写,他会像今天一样毫不犹豫地回答。软件应该用C++写。但如果语言都是等价的,为什么尖头发老板的观点会改变?事实上,为什么Java的开发者还要费心创造一种新语言?

大概,如果你创造一种新语言,是因为你认为它在某些方面比人们已有的更好。事实上,Gosling在第一份Java白皮书中明确表示,Java是为了解决C++的一些问题而设计的。所以你看:语言并不都是等价的。如果你顺着尖头发老板大脑中的思路,从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年末的某个时候,McCarthy的研究生Steve Russell看着这个eval的定义,意识到如果他把它翻译成机器语言,结果就会是一个Lisp解释器。

这在当时是一个很大的惊喜。McCarthy后来在一次采访中这样说到:Steve Russell说,看,为什么不把这个eval…编程出来,我对他说,呵呵,你把理论和实践搞混了,这个eval是用来阅读的,不是用来计算的。但他还是去做了。也就是说,他把论文中的eval编译成了[IBM] 704机器代码,修复了bug,然后把它宣传为Lisp解释器,它确实就是。所以在那时Lisp基本上就有了今天的形式…突然间,我想是在几周内,McCarthy发现他的理论练习变成了一个实际的编程语言——而且比他预期的更强大。

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

还有另一种从1950年代存活下来的语言,Fortran,它代表了语言设计的相反方法。Lisp是一个意外变成编程语言的理论。Fortran是故意作为编程语言开发的,但按我们现在的标准来看是一个很低级的语言。

Fortran I,1956年开发的语言,与现在的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,然后传播到它们的所有后代。

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

  2. 使用符号和常量树来表示代码的表示法。

  3. 整个语言随时可用。在读取时、编译时和运行时之间没有真正的区别。你可以在读取时编译或运行代码,在编译时读取或运行代码,在运行时读取或编译代码。

在读取时运行代码让用户可以重新编程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的任何代码,但根据他们的一位顶级黑客说,他们使用了很多宏,我听到这一点并不感到惊讶。

向心力

我不是说使用不常见的技术没有成本。尖头发老板担心这一点并不是完全错误的。但因为他不理解风险,他倾向于夸大它们。

我能想到使用不太常见的语言可能出现的三个问题。你的程序可能与其他语言写的程序配合不好。你可能会有更少的库可用。你可能很难招聘程序员。

这些问题各有多大?第一个问题的重要性取决于你是否控制整个系统。如果你写的软件必须在远程用户的机器上运行,在一个有bug的、封闭的操作系统之上(我不提名字),用与操作系统相同的语言写你的应用程序可能有优势。但如果你控制整个系统,有所有部分的源代码,就像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写你的软件,他们能够以比你快20倍的速度开发软件。如果你花一年时间开发一个新功能,他们能够在不到三周内复制它。而如果他们只花三个月开发新东西,你要花五年才能有同样的东西。

你知道什么吗?这是最好的情况。当你谈论代码大小比率时,你隐含地假设你实际上可以用较弱的语言写程序。但事实上程序员能做什么是有限制的。如果你试图用太低级的功能解决一个困难的问题,你会达到一个点,那里有太多东西要同时记在脑子里。

所以当我说ITA的假想竞争对手要花五年才能复制ITA用Lisp三个月就能写出来的东西时,我指的是如果一切顺利的话五年。事实上,在大多数公司的工作方式中,任何需要五年的开发项目都可能永远不会完成。

我承认这是一个极端情况。ITA的黑客似乎异常聪明,而C是一个相当低级的语言。但在竞争市场中,即使是2-3倍的差异也足以保证你总是落后。

一个配方

这是尖头发老板甚至不想考虑的那种可能性。所以大多数人都不会。因为,你知道,归根结底,尖头发老板不介意他的公司被击败,只要没有人能证明这是他的错。对他来说最安全的计划是紧贴群体的中心。

在大型组织中,用来描述这种方法的短语是"行业最佳实践"。它的目的是保护尖头发老板免受责任:如果他选择"行业最佳实践"的东西,而公司输了,他不能被指责。他没有选择,是行业选择的。

我相信这个词最初是用来描述会计方法等的。它的意思大致是不要做任何奇怪的事情。在会计中这可能是个好主意。“尖端"和"会计"这两个词听起来不太好。但当你把这个标准引入技术决策时,你开始得到错误的答案。

技术往往应该是尖端的。在编程语言方面,正如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版本有更多的元素,因为你在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方式处理它,让你可以省略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,他们可以总是…)

(Oscar Wilde:“我希望我说过那句话。” Whistler:“你会说的,Oscar,你会说的。")

在面向对象语言中,你可以通过定义一个类来有限地模拟闭包(一个引用外部作用域中定义的变量的函数),类有一个方法和一个字段来替代每个外部作用域中的变量。这使程序员做编译器在完全支持词法作用域的语言中会做的那种代码分析,如果多个函数引用同一个变量它就不会工作,但对于像这样的简单情况它足够了。

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程序都包含一个非正式的、有bug的、缓慢的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: 极客的复仇

它也引发了LL1邮件列表上的广泛且经常有用的讨论。特别参见Anton van Straaten关于语义压缩的邮件。

LL1上的一些邮件促使我尝试更深入地探讨语言威力这个主题,在简洁就是力量中。

一个更大的累加器生成器基准的规范实现集合被收集在它们自己的页面上。

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

英文版:paulgraham.com/icad.html|中文版:HiJiangChuan.com/paulgraham/017-revenge-of-the-nerds

📚 返回 Paul Graham 文章目录

更新记录: