Loading...

极客的复仇

Original

May 2002

“我们想要的是 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 机器代码,修复了 bug,然后把它宣传成一个 Lisp 解释器,它确实是一个解释器。所以,从那时起,Lisp 就基本上有了它今天的样子....

突然之间,我想是在几周内,McCarthy 发现他的理论练习变成了一个真正的编程语言——而且比他预期的更强大。

所以,为什么这种 1950 年代的语言没有过时,简短的解释是它不是技术,而是数学,而数学不会过时。与 Lisp 相比较的不是 1950 年代的硬件,而是,比如,Quicksort 算法,它是在 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 应用程序的核心是一个 200,000 行的 Common Lisp 程序,它搜索的可能性比竞争对手多出几个数量级,而竞争对手显然 仍在使用大型机时代的编程技术。 (尽管 ITA 在某种意义上 也使用了一种大型机时代的编程语言。) 我从未见过 ITA 的任何代码,但据他们的一位顶级黑客说,他们使用了大量的宏, 我听到这个消息并不感到惊讶。

向心力

我并不是说使用不常见的 技术没有成本。尖头老板担心这一点并非完全没有道理。但他不理解 风险,因此往往会夸大风险。

我可以想到使用 不常见语言可能会出现的三种问题。你的程序可能无法与 用其他语言编写的程序很好地协同工作。你可能可以使用更少的 库。你可能难以 雇佣程序员。

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

在基于服务器的应用程序中,你可以 使用最先进的技术,我认为这是 Jonathan Erickson 所谓的“编程语言 复兴." 这就是我们甚至会听到 Perl 和 Python 等新 语言的原因。我们听到这些 语言的原因不是因为人们用它们来编写 Windows 应用程序,而是因为人们在服务器上使用它们。随着软件的转移 离开桌面并进入服务器(即使 微软似乎也已经接受了这种未来),使用中庸技术的压力会越来越小。

至于库,它们的重要性也 取决于应用程序。对于要求较低的问题, 库的可用性可能超过语言的内在能力。盈亏平衡点在哪里?很难说 确切地说,但无论它在哪里,它都低于你可能称之为应用程序的任何东西。如果一家公司认为 自己处于软件行业,并且他们正在编写 一个将成为他们产品的应用程序, 那么它可能涉及几个黑客,并且至少需要六个月才能编写。在一个这样的项目中, 功能强大的语言可能开始超过 现有库的便利性。

尖头老板的第三个担忧,雇佣程序员的难度,我认为是一个红鲱鱼。毕竟,你需要雇佣多少 黑客?到目前为止,我们 都应该知道,软件最好由不到十人的团队开发。而且,你应该不会在任何语言中遇到雇佣 黑客的困难,无论谁听说过这种语言。如果你找不到十个 Lisp 黑客,那么你的公司可能 位于不适合开发软件的城市。

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

我并不是说你不会受到很大的压力,要求你使用 被认为是“标准”的技术。在 Viaweb (现在是 Yahoo Store), 我们使用 Lisp,这在风投和潜在收购者中引起了不少关注。但我们也因为使用 通用英特尔盒子作为服务器而不是 “工业级”服务器,比如 Sun 公司的服务器,因为使用 当时默默无闻的开源 Unix 变体 FreeBSD 而不是 像 Windows NT 这样的真正的商业操作系统,因为忽略了 一个所谓的电子商务标准,叫做 SET,现在没有人 记得了,等等。

你不能让那些西装革履的人为你做出技术决策。 我们使用 Lisp 会让一些潜在的收购者感到担忧吗?有些,稍微有点, 但如果我们没有使用 Lisp,我们就不会 能够编写出让他们想要收购我们的软件。对他们来说,这似乎是一个异常现象,但实际上 是因果关系。

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

平庸的代价

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

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

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

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

那么,如果你用 Lisp 编写程序,你的程序会短多少?我听到的大多数关于 Lisp 与 C 的比较数字,例如,都在 7-10 倍左右。 但最近一篇关于 ITA 的文章 新 架构 杂志说,“一行 Lisp 可以替换 20 行 C”,而且由于 这篇文章充满了来自 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 也保留了 这种区别,但通过让你省略 return 来处理它,这是典型的 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 程序都包含一个临时 非正式指定、有 bug、速度慢的 Common Lisp 的一半实现。

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

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

注释

IBM 704 CPU 的大小大约相当于一台冰箱, 但要重得多。CPU 重 3150 磅, 而 4K 的 RAM 则放在一个单独的 箱子里,重 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。

感谢许多人回答了我关于各种语言的问题,或者阅读了本文的草稿,包括肯·安德森、特雷弗·布莱克韦尔、埃兰·加特、丹·吉芬、莎拉·哈林、杰里米·希尔顿、罗伯特·莫里斯、彼得·诺维格、盖伊·斯蒂尔和安东·范·斯特拉滕。 他们对任何表达的意见不负任何责任。

相关:

许多人对这次演讲做出了回应, 所以我创建了一个额外的页面来处理他们提出的问题:Re: 极客的复仇

它还引发了关于 LL1 邮件列表的广泛而有用的讨论。特别参见安东·范·斯特拉滕关于语义压缩的邮件。

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

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

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