书呆子的逆袭

想创办一家创业公司吗? 获得 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 截然相反的语言设计方法。Lisp 是一项理论,意外地变成了一门编程语言。Fortran 则是有意作为一门编程语言开发的,但按照我们现在的标准,它是一门非常低级的语言。

Fortran I,这门在1956年开发的语言,与今天的 Fortran 大相径庭。Fortran I 基本上就是带有数学功能的汇编语言。在某些方面,它比最近的汇编语言功能更弱;例如,没有子程序,只有分支。今天的 Fortran 现在可以说比 Fortran I 更接近 Lisp。

Lisp 和 Fortran 是两棵独立演化树的树干,一棵植根于数学,一棵植根于机器架构。此后这两棵树一直在趋同。Lisp 最初功能强大,在接下来的二十年里变得快速。所谓的主流语言最初速度很快,在接下来的四十年里逐渐变得更强大,直到现在,其中最先进的语言已经相当接近 Lisp。接近了,但它们仍然缺少一些东西……

Lisp 的独特之处

Lisp 最初开发时,包含了九个新思想。其中一些我们现在习以为常,另一些只在更高级的语言中才能看到,还有两个仍然是 Lisp 独有的。这九个思想,按其被主流采纳的顺序是,

  1. 条件语句。条件语句是一种 if-then-else 结构。我们现在认为这些是理所当然的,但 Fortran I 没有它们。它只有一种基于底层机器指令的条件跳转。

  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 的任何代码,但据他们的一位顶级黑客说,他们使用了大量的宏,听到这个我并不感到惊讶。

向心力

我并不是说使用不常见的技术没有成本。尖头老板对此的担忧并非完全错误。但因为他不理解风险,他往往会夸大它们。

我想到了使用不常见语言可能出现的三个问题。你的程序可能无法与其他语言编写的程序良好协作。你可能拥有的库更少。而且你可能在招聘程序员方面遇到麻烦。

这些问题中的每一个有多严重?第一个问题的重要性取决于你是否控制整个系统。如果你编写的软件必须在远程用户的机器上运行,并且是在一个有缺陷的、封闭的操作系统之上(我不点名),那么用与操作系统相同的语言编写应用程序可能具有优势。但如果你控制整个系统并拥有所有部分的源代码,就像 ITA 可能做的那样,你可以使用任何你想要的语言。如果出现任何不兼容,你可以自己修复它。

在基于服务器的应用程序中,你可以放心地使用最先进的技术,我认为这是 Jonathan Erickson 所称的“编程语言复兴”的主要原因。这就是为什么我们甚至会听说像 Perl 和 Python 这样的新语言。我们听说这些语言不是因为人们用它们来编写 Windows 应用程序,而是因为人们在服务器上使用它们。随着软件 从桌面 转移到服务器(即使是 Microsoft 似乎也已接受的未来),使用中庸技术的压力将越来越小。

至于库,它们的重要性也取决于应用程序。对于要求不那么高的问题,库的可用性可能超过语言本身的内在能力。盈亏平衡点在哪里?很难确切地说,但无论它在哪里,都远未达到你可能称之为应用程序的程度。如果一家公司认为自己从事软件业务,并且他们正在编写一个将成为其产品的应用程序,那么它可能涉及数名黑客,并且至少需要六个月才能编写完成。在如此规模的项目中,强大的语言可能开始超过现有库的便利性。

尖头老板的第三个担忧,即招聘程序员的困难,我认为是一个转移注意力的东西。毕竟,你需要招聘多少黑客呢?现在我们都应该知道,软件最好由少于十人的团队开发。而且,对于任何听说过的语言,你都不应该在那种规模上招聘黑客遇到麻烦。如果你找不到十个 Lisp 黑客,那么你的公司所在地可能不适合开发软件。

事实上,选择一门更强大的语言可能会减少你所需团队的规模,因为 (a) 如果你使用一门更强大的语言,你可能不需要那么多黑客,而且 (b) 使用更高级语言的黑客可能更聪明。

我并不是说你不会面临使用被认为是“标准”技术的巨大压力。在 Viaweb(现在的 Yahoo Store),我们使用 Lisp 引起了风险投资家和潜在收购方的一些侧目。但我们还因为使用普通的 Intel 盒子作为服务器而不是像 Suns 那样的“工业级”服务器,因为使用当时鲜为人知的开源 Unix 变体 FreeBSD 而不是像 Windows NT 那样的真正的商业操作系统,因为忽略了一个现在甚至没有人记得的所谓电子商务标准 SET 等等而引起了侧目。

你不能让那些西装革履的人替你做技术决策。我们使用 Lisp 是否让一些潜在收购方感到担忧?有些,略微,但如果我们没有使用 Lisp,我们就无法编写出让他们想要收购我们的软件。对他们来说看似异常的东西,实际上是因果关系。

如果你创办一家创业公司,不要为了取悦风险投资家或潜在收购方而设计你的产品。设计你的产品以取悦用户。 如果你赢得了用户,其他一切都会随之而来。如果你没有,没有人会关心你的技术选择是多么令人安心的正统。

平庸的代价

使用功能较弱的语言你会损失多少?实际上有一些关于这方面的数据。

衡量能力最方便的指标可能是 代码量。高级语言的目的是为你提供更大的抽象——可以说,是更大的砖块,这样你就不需要那么多砖块来建造一面给定大小的墙。所以,语言越强大,程序就越短(当然,不仅仅是字符数,而是独立元素的数量)。

更强大的语言如何让你编写更短的程序?如果你使用的语言允许,你可以使用一种叫做 自底向上编程 的技术。你不是简单地用基础语言编写你的应用程序,而是在基础语言之上构建一种用于编写像你这样的程序的语言,然后用它来编写你的程序。组合后的代码可以比你用基础语言编写整个程序短得多——事实上,大多数压缩算法就是这样工作的。自底向上程序也应该更容易修改,因为在许多情况下,语言层根本不需要改变。

代码量很重要,因为编写程序所需的时间主要取决于它的长度。如果你的程序用另一种语言编写会是三倍长,那么编写它所需的时间也会是三倍——你无法通过雇用更多人来解决这个问题,因为超过一定规模后,新员工实际上是净损失。Fred Brooks 在他著名的著作《人月神话》中描述了这种现象,我所看到的一切都倾向于证实他所说的。

那么,如果你用 Lisp 编写程序,你的程序会短多少?例如,我听到的 Lisp 与 C 相比的大多数数字都在7-10倍左右。但最近一篇关于 ITA 的文章在《New Architect》杂志上说,“一行 Lisp 可以替代20行 C”,而且由于这篇文章充满了 ITA 总裁的引语,我猜他们是从 ITA 那里得到这个数字的。如果是这样,那么我们可以相信它;ITA 的软件包含大量的 C 和 C++ 以及 Lisp,所以他们是根据经验发言。

我猜这些倍数甚至不是恒定的。我认为当你面临更困难的问题时,以及当你拥有更聪明的程序员时,它们会增加。一个真正优秀的黑客可以从更好的工具中榨取更多价值。

无论如何,作为曲线上的一个数据点,如果你要与 ITA 竞争并选择用 C 编写你的软件,他们将能够比你快二十倍地开发软件。如果你花一年时间开发一个新功能,他们将能够在不到三周的时间内复制它。而如果他们只花三个月开发一些新东西,你也要 五年 才能拥有它。

你知道吗?这还是最好的情况。当你谈论代码量比率时,你隐含地假设你实际上可以用较弱的语言编写程序。但事实上,程序员能做的事情是有限的。如果你试图用一门太低级的语言解决一个难题,你会达到一个点,即一次性要记住的东西太多了。

所以当我说 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 版本有更多元素,因为在 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 的其余部分,他们总是可以……)

在面向对象语言中,你可以在有限的程度上模拟一个闭包(一个引用封闭作用域中定义的变量的函数),方法是定义一个带有一个方法和一个字段的类,以替换封闭作用域中的每个变量。这使得程序员必须进行编译器在完全支持词法作用域的语言中会进行的的那种代码分析,而且如果多个函数引用同一个变量,它将不起作用,但在这种简单情况下足够了。

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 程序都包含一个临时性的、非正式指定、充满 bug 的缓慢实现的半个 Common Lisp。

如果你试图解决一个难题,问题不在于你是否会使用足够强大的语言,而在于你是否会 (a) 使用一种强大的语言,(b) 为其编写一个事实上的解释器,或者 (c) 自己成为一个“人肉编译器”。我们已经看到这在 Python 示例中开始发生,我们在其中实际上模拟了编译器为实现词法变量而生成的代码。

这种做法不仅常见,而且已经制度化。例如,在面向对象的世界里,你会听到很多关于“设计模式”的讨论。我怀疑这些模式有时是否是案例 (c),即人肉编译器在工作,的证据。当我在我的程序中看到模式时,我认为这是一个麻烦的迹象。程序的形态应该只反映它需要解决的问题。代码中的任何其他规律性,至少对我来说,都表明我使用的抽象不够强大——通常是我正在手动生成我需要编写的某个宏的扩展。

注释

  • IBM 704 CPU 大约有冰箱那么大,但重得多。CPU 重3150磅,4K 的内存则在一个单独的箱子里,重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。他们对本文表达的任何观点不承担任何责任。

相关文章:

许多人对这次演讲做出了回应,所以我设立了一个附加页面来处理他们提出的问题:回复:书呆子的逆袭

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

LL1 上的一些邮件促使我尝试在《简洁即力量》中更深入地探讨语言能力的主题。

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

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