我的 Lisp 经验与 GNU Emacs 的开发

理查德·斯托曼在 2002 年 10 月 28 日国际 Lisp 会议上的演讲稿。


因为我通常的演讲都与 Lisp 无关,所以它们都不适合今天。所以我得即兴发挥了。因为我职业生涯中做了很多与 Lisp 相关的事情,所以我应该能说出一些有趣的东西。

我第一次接触 Lisp 是在高中时阅读 Lisp 1.5 手册。那时,我被这样一种计算机语言的想法所震撼。我第一次有机会使用 Lisp 是在哈佛大学读大一时,我为PDP-11 编写了一个 Lisp 解释器。那是一个非常小的机器,只有大约 8k 的内存,我设法用一千条指令编写了解释器。这给了我一些空间来存储少量数据。那是在我看到真正的软件是什么样子,以及它如何完成真正的系统工作之前。

当我开始在MIT工作时,我开始与 JonL White 一起进行真正的 Lisp 实现工作。我被人工智能实验室聘用,不是由 JonL,而是由 Russ Noftsker 聘用的,考虑到之后发生的事情,这真是太讽刺了——他肯定很后悔那天。

在 1970 年代,在我的生活因可怕的事件而变得政治化之前,我只是在为各种程序做一个又一个的扩展,其中大多数与 Lisp 无关。但是,在此过程中,我编写了一个文本编辑器 Emacs。Emacs 有趣的想法是,它有一种编程语言,用户的编辑命令将用该解释型编程语言编写,这样您就可以在编辑时将新命令加载到编辑器中。您可以编辑您正在使用的程序,然后继续用它们进行编辑。所以,我们有一个系统,它不仅可以用于编程,而且您可以在使用它时进行编程。我不知道它是不是第一个这样的系统,但它肯定是第一个这样的编辑器。

这种构建庞大而复杂的程序以用于自己的编辑,然后与其他人交换它们的精神,激发了当时我们在人工智能实验室的自由合作精神。我们的想法是,你可以将你拥有的任何程序的副本提供给任何想要它的人。我们与任何想使用它们的人共享程序,它们是人类的知识。因此,尽管没有有组织的政治思想将我们共享软件的方式与 Emacs 的设计联系起来,但我相信它们之间存在着联系,也许是一种无意识的联系。我认为,我们在人工智能实验室的生活方式的本质导致了 Emacs 的出现,并使其成为现在的样子。

最初的 Emacs 中没有 Lisp。底层语言,非解释型语言,是 PDP-10 汇编语言。我们在其中编写的解释器实际上不是为 Emacs 编写的,而是为TECO编写的。它是我们的文本编辑器,是一种极其丑陋的编程语言,丑陋得不能再丑陋了。原因是它不是被设计为编程语言,而是被设计为编辑器和命令语言。有一些命令,比如 5l,意思是 移动五行,或者 i,然后是一个字符串,然后是一个 ESC 键来插入该字符串。您会键入一个字符串,它是一系列命令,称为命令字符串。您将以 ESC ESC 结尾,它将被执行。

好吧,人们希望用编程工具扩展这种语言,所以他们添加了一些。例如,最早的一个是循环结构,它是 < >。您会将它们放在事物周围,它就会循环。还有其他神秘的命令可以用来有条件地退出循环。为了制作 Emacs,我们 [1] 添加了具有名称的子例程的功能。在此之前,它有点像 Basic,子例程的名称只能是单个字母。这很难用它编写大型程序,所以我们添加了代码,以便它们可以使用更长的名称。实际上,有一些相当复杂的功能;我认为 Lisp 的 unwind-protect 功能是从 TECO 那里得到的。

我们开始添加相当复杂的功能,所有这些功能的语法都丑陋至极,但它确实奏效了——人们仍然能够用它编写大型程序。显而易见的教训是,像 TECO 这样并非设计为编程语言的语言是错误的。您在其上构建扩展的语言不应被视为事后的编程语言;它应该被设计为编程语言。事实上,我们发现用于此目的的最佳编程语言是 Lisp。

是 Bernie Greenberg 发现的 [2]。他用 Multics MacLisp 编写了一个 Emacs 版本,并且他以一种直接的方式用 MacLisp 编写了他的命令。编辑器本身完全是用 Lisp 编写的。Multics Emacs 被证明非常成功——编程新的编辑命令非常方便,甚至他办公室的秘书们都开始学习如何使用它。他们使用一本别人写的指南,其中显示了如何扩展 Emacs,但没有说这是一个编程。因此,那些认为自己无法编程的秘书们并没有被吓退。他们阅读了指南,发现他们可以做有用的事情,并且他们学会了编程。

因此,Bernie 看到一个应用程序——一个为你做有用的事情的程序——它内部有 Lisp,并且你可以通过重写 Lisp 程序来扩展它,实际上是人们学习编程的一个非常好的方法。它让他们有机会编写对他们有用的的小程序,这在大多数领域你是不可能做到的。他们可以得到自己实际使用的鼓励——在最困难的阶段——在他们相信自己可以编程之前,直到他们成为程序员的那一刻。

那时,人们开始想知道他们如何在没有完整 Lisp 实现的平台上获得类似的东西。Multics MacLisp 既有编译器也有解释器——它是一个成熟的 Lisp 系统——但是人们希望在其他没有编写 Lisp 编译器的系统上实现类似的东西。好吧,如果你没有 Lisp 编译器,你就不能用 Lisp 编写整个编辑器——它会太慢,尤其是重新显示,如果它必须运行解释的 Lisp。因此,我们开发了一种混合技术。其思想是将 Lisp 解释器和编辑器的较低级别部分一起编写,以便编辑器的某些部分是内置的 Lisp 功能。这些将是我们认为必须优化的任何部分。这是我们在原始 Emacs 中已经有意识地实践的一种技术,因为有一些相当高级的功能,我们用机器语言重新实现了它们,使它们成为 TECO 原语。例如,有一个 TECO 原语来填充段落(实际上,是为了完成填充段落的大部分工作,因为该工作中一些不太耗时的部分将由 TECO 程序在更高层完成)。您可以通过编写 TECO 程序来完成整个工作,但这太慢了,所以我们通过将其中一部分放入机器语言中来优化它。我们在这里(在混合技术中)使用了相同的想法,即编辑器的大部分将用 Lisp 编写,但是其中某些必须运行得特别快的部分将以较低级别编写。

因此,当我编写我的第二个 Emacs 实现时,我遵循了相同的设计。低级语言不再是机器语言,而是 C。C 是一种用于在类 Unix 操作系统中运行的可移植程序的高效语言。有一个 Lisp 解释器,但我直接用 C 实现了用于特殊用途的编辑作业的功能——操作编辑器缓冲区、插入前导文本、读取和写入文件、在屏幕上重新显示缓冲区、管理编辑器窗口。

现在,这不是第一个用 C 编写并在 Unix 上运行的 Emacs。第一个是由 James Gosling 编写的,被称为 GosMacs。他身上发生了一件奇怪的事情。一开始,他似乎受到了原始 Emacs 的共享和合作精神的影响。我首先将原始 Emacs 发布给了 MIT 的人们。有人想把它移植到 Twenex 上运行——它最初只在我们在 MIT 使用的不兼容的分时系统上运行。他们把它移植到了 Twenex,这意味着世界上有数百个安装可能会使用它。我们开始将它分发给他们,并规定“你必须把你的所有改进都发回来”,这样我们才能都受益。从来没有人试图强制执行这一点,但据我所知,人们确实进行了合作。

高斯林最初似乎也参与了这种精神。他在一本他称为 Emacs 的手册中写道,希望社区中的其他人能够改进它,直到它配得上这个名字。这才是对待社区的正确方式——邀请他们加入并使程序变得更好。但在那之后,他似乎改变了这种精神,并把它卖给了一家公司。

当时我正在开发 GNU 系统(一个类 Unix 的自由软件操作系统,很多人错误地称之为“Linux”)。当时没有能在 Unix 上运行的自由软件 Emacs 编辑器。但我有一个朋友参与了高斯林的 Emacs 开发。高斯林通过电子邮件允许他分发自己的版本。他建议我使用那个版本。然后我发现高斯林的 Emacs 没有真正的 Lisp。它有一种被称为“mocklisp”的编程语言,它在语法上看起来像 Lisp,但没有 Lisp 的数据结构。因此,程序不是数据,Lisp 的重要元素都缺失了。它的数据结构是字符串、数字和其他一些特殊的东西。

我得出结论,我不能使用它,必须全部替换它,第一步是编写一个真正的 Lisp 解释器。我逐渐根据真正的 Lisp 数据结构,而不是临时的 ad hoc 数据结构,调整了编辑器的每个部分,使编辑器的内部数据结构可以被用户的 Lisp 程序公开和操作。

唯一的例外是重绘。很长一段时间,重绘就像一个独立的世界。编辑器会进入重绘的世界,事情会按照非常特殊的数据结构进行,这些数据结构对于垃圾回收是不安全的,对于中断是不安全的,而且你不能在那里运行任何 Lisp 程序。我们后来改变了这一点——现在可以在重绘期间运行 Lisp 代码了。这非常方便。

第二个 Emacs 程序是现代意义上的“自由软件”——它是让软件自由的明确政治运动的一部分。这场运动的本质是,每个人都应该可以自由地做我们过去在麻省理工学院做的事情,一起合作开发软件,并与任何愿意与我们合作的人合作。这是自由软件运动的基础——我在麻省理工学院人工智能实验室的经历,我所过的生活——致力于人类知识,而不阻碍任何人进一步使用和传播人类知识。

当时,你可以制造一台计算机,它的价格范围与其他非 Lisp 计算机差不多,但它运行 Lisp 的速度比它们快得多,而且在每次操作中都有完整的类型检查。普通的计算机通常迫使你在执行速度和良好的类型检查之间做出选择。所以,是的,你可以有一个 Lisp 编译器并快速运行你的程序,但是当它们试图对一个数字进行 car 操作时,会得到毫无意义的结果,最终在某个时候崩溃。

Lisp 机器能够以与其他机器差不多的速度执行指令,但每条指令——一个 car 指令会进行数据类型检查——因此,当你试图在一个编译后的程序中获取一个数字的 car 时,它会立即给出错误。我们制造了这台机器,并为它配备了一个 Lisp 操作系统。它几乎完全用 Lisp 编写,唯一的例外是用微码编写的部分。人们开始对制造它们感兴趣,这意味着他们应该成立一家公司。

关于这家公司应该是什么样子,有两种不同的想法。格林布拉特想创办一家他所谓的“黑客”公司。这意味着它将是一家由黑客运营的公司,并以有利于黑客的方式运作。另一个目标是保持人工智能实验室的文化 [3]。不幸的是,格林布拉特没有任何商业经验,所以 Lisp 机器小组的其他成员表示他们怀疑他是否能成功。他们认为他避免外部投资的计划行不通。

他为什么要避免外部投资?因为当一家公司有外部投资者时,他们会控制公司,并且不让你有任何顾虑。最终,如果你有任何顾虑,他们也会取代你成为经理。

所以格林布拉特的想法是找到一个客户预付购买零件的费用。他们将制造机器并交付;用这些零件的利润,他们将能够购买更多机器的零件,出售这些机器,然后购买更多数量机器的零件,依此类推。小组中的其他人认为这不可能奏效。

然后,格林布拉特招募了拉塞尔·诺夫茨克,那个雇佣我的人,他后来离开了人工智能实验室并创建了一家成功的公司。人们认为拉塞尔有商业头脑。他通过对小组中的其他人说:“让我们抛弃格林布拉特,忘记他的想法,然后我们再成立一家公司。” 来展示了他的商业头脑。背后捅刀,显然是个真正的商人。这些人决定成立一家名为 Symbolics 的公司。他们会获得外部投资,不顾顾虑,尽一切可能去赢。

但格林布拉特没有放弃。他和少数忠于他的人决定无论如何都要成立 Lisp Machines Inc. 并继续他们的计划。结果你知道吗,他们成功了!他们获得了第一个客户并预付了款项。他们制造机器并出售,然后制造更多的机器。尽管他们没有小组中大多数人的帮助,但他们实际上成功了。Symbolics 也取得了成功的开端,因此出现了两家竞争的 Lisp 机器公司。当 Symbolics 看到 LMI 不会一蹶不振时,他们开始寻找摧毁它的方法。

因此,我们实验室的放弃之后是实验室的“战争”。当 Symbolics 挖走了所有的黑客时,放弃发生了,除了我和在 LMI 兼职的少数人。然后他们援引一项规则,解除了在麻省理工学院兼职的人的职务,所以他们不得不完全离开,只剩下我。人工智能实验室现在无能为力了。麻省理工学院与这两家公司达成了非常愚蠢的协议。这是一个三方合同,两家公司都获得了 Lisp 机器系统源代码的使用许可。这些公司被要求让麻省理工学院使用他们的更改。但是合同中没有说麻省理工学院有权将它们放入两家公司都获得许可的麻省理工学院 Lisp 机器系统中。没有人预料到人工智能实验室的黑客小组会被消灭,但它确实被消灭了。

所以 Symbolics 想出了一个计划 [4]。他们对实验室说:“我们将继续使我们对系统的更改可供您使用,但您不能将其放入麻省理工学院的 Lisp 机器系统中。相反,我们将让您访问 Symbolics 的 Lisp 机器系统,您可以运行它,但仅此而已。”

实际上,这意味着他们要求我们必须选择一方,要么使用麻省理工学院版本的系统,要么使用 Symbolics 版本的系统。我们做出的任何选择都决定了我们的改进会进入哪个系统。如果我们开发并改进 Symbolics 版本,我们将只支持 Symbolics。如果我们使用并改进麻省理工学院版本的系统,我们将做的工作可供两家公司使用,但 Symbolics 认为我们将支持 LMI,因为我们将帮助他们继续存在。所以我们不再被允许保持中立。

直到那时,我还没有选择任何一方,尽管看到我们的社区和软件发生了什么让我感到很痛苦。但现在,Symbolics 迫使我做出选择。因此,为了帮助 Lisp Machines Inc. 继续发展 [5]——我开始复制 Symbolics 对 Lisp 机器系统所做的所有改进。我自己再次编写了等效的改进(即,代码是我自己的)。

过了一段时间 [6],我得出的结论是,最好我甚至不要看他们的代码。当他们发布一个包含发布说明的测试版公告时,我会看到这些功能是什么,然后实现它们。当他们有真正的发布时,我也有了。

这样,两年里,我阻止了他们消灭 Lisp Machines Incorporated,两家公司继续发展。但是,我不想花几年时间惩罚某人,只是为了阻止一个邪恶的行为。我认为他们受到了相当彻底的惩罚,因为他们陷入了一场不会离开或消失的竞争 [7]。与此同时,是时候开始建立一个新的社区来取代他们的行为和其他人的行为所摧毁的社区了。

70 年代的 Lisp 社区不仅限于麻省理工学院的人工智能实验室,黑客也不都在麻省理工学院。Symbolics 发动的战争摧毁了麻省理工学院,但当时也发生了其他事件。有些人放弃了合作,他们共同摧毁了社区,剩下的不多了。

一旦我停止惩罚 Symbolics,我就必须弄清楚下一步该做什么。我必须制作一个自由操作系统,这很清楚——人们能够一起工作和分享的唯一方式是使用自由操作系统。

起初,我考虑制作一个基于 Lisp 的系统,但我意识到这在技术上不是一个好主意。要拥有像 Lisp 机器系统这样的东西,你需要专门的微码。这使得程序能够以与其他计算机运行程序的速度一样快地运行,并且仍然可以获得类型检查的好处。如果没有这些,你将会退化到像其他机器的 Lisp 编译器那样的东西。程序会更快,但不稳定。现在,如果你在分时系统上运行一个程序,这没问题——如果一个程序崩溃了,那不是灾难,那是你的程序偶尔会做的事情。但那不适合用来编写操作系统,所以我否决了制作像 Lisp 机器一样的系统的想法。

我决定创建一个类 Unix 操作系统,并在其中运行 Lisp 实现作为用户程序。内核不会用 Lisp 编写,但我们会拥有 Lisp。因此,这个操作系统的开发,即 GNU 操作系统,促使我编写了 GNU Emacs。在这样做时,我的目标是实现尽可能精简的 Lisp 实现。程序的大小是一个非常重要的考量。

在 1985 年的那个年代,有些人拥有一兆字节的机器,而且没有虚拟内存。他们希望能够使用 GNU Emacs。这意味着我必须尽可能地缩小程序的大小。

例如,当时唯一的循环结构是 while,它非常简单。没有办法跳出 while 语句,你只能使用 catch 和 throw,或者测试一个运行循环的变量。这表明为了保持代码的小巧,我做出了多大的努力。我们没有 caarcadr 等等;“尽可能地挤出每一滴”是 GNU Emacs 的精神,也是 Emacs Lisp 从一开始就有的精神。

显然,现在的机器更大了,我们不再那样做了。我们加入了 caarcadr 等等,我们可能在未来的某一天会加入另一个循环结构。我们现在愿意做一些扩展,但我们不想扩展到 Common Lisp 的水平。我曾经在 Lisp 机器上实现过 Common Lisp,我对此并不太满意。其中一件我不太喜欢的事情是关键字参数[8]。它们对我来说不太像 Lisp;我有时会使用它,但我会尽量减少使用的次数。

这并不是 GNU 项目中与 Lisp 相关的最后一个项目。大约在 1995 年左右,我们开始研究启动一个图形桌面项目。很明显,对于桌面上的程序,我们希望有一种编程语言来编写其中的大部分代码,使其像编辑器一样易于扩展。问题是应该选择哪种语言。

当时,TCL 被大力推广用于此目的。我对 TCL 的评价很低,基本上是因为它不是 Lisp。它看起来有点像 Lisp,但在语义上不是,而且没有那么简洁。然后有人给我看了一个广告,Sun 公司试图雇人开发 TCL,使其成为世界上“事实上的标准扩展语言”。我想,“我们必须阻止这种情况发生。” 因此,我们开始将 Scheme 作为 GNU 的标准可扩展性语言。而不是 Common Lisp,因为它太大了。我们的想法是,我们将有一个 Scheme 解释器,它被设计成以 TCL 链接到应用程序的相同方式链接到应用程序中。然后,我们将建议它作为所有 GNU 程序首选的可扩展性包。

使用像 Lisp 这样强大的语言作为你的主要可扩展性语言,你可以获得一个有趣的好处。你可以通过将其他语言翻译成你的主要语言来实现它们。如果你的主要语言是 TCL,你不能很容易地通过将 Lisp 翻译成 TCL 来实现 Lisp。但是如果你的主要语言是 Lisp,那么通过翻译来实现其他东西并不难。我们的想法是,如果每个可扩展的应用程序都支持 Scheme,你可以在 Scheme 中编写 TCL 或 Python 或 Perl 的实现,将该程序翻译成 Scheme。然后,你可以将其加载到任何应用程序中,并使用你喜欢的语言进行自定义,并且它也可以与其他自定义一起工作。

只要可扩展性语言很弱,用户就只能使用你提供给他们的语言。这意味着任何喜欢某种语言的人都必须竞争应用程序开发人员的选择——说“请,应用程序开发人员,把我的语言放到你的应用程序中,而不是他的语言。” 然后用户就没有任何选择了——他们使用的任何应用程序都带有一种语言,他们只能被困在[该语言]中。但是,当你拥有一种强大的语言,可以通过翻译实现其他语言时,你就可以让用户选择语言,而我们不必再进行语言战争了。这就是我们希望 Guile(我们的 Scheme 解释器)所做的事情。我们去年夏天有一位工作人员在完成一个从 Python 到 Scheme 的翻译器。我不知道它是否完全完成了,但是对于任何对这个项目感兴趣的人,请联系我们。这就是我们对未来的计划。

我还没有谈论自由软件,但请允许我简要地告诉大家一下它的含义。自由软件不是指价格;它不是指你可以免费获得它。(你可能为副本付费,或者免费获得副本。)它意味着你作为用户拥有自由。关键是你拥有运行程序的自由、研究它做什么的自由、根据你的需要更改它的自由、重新分发其他人的副本的自由以及发布改进的、扩展版本的自由。这就是自由软件的含义。如果你正在使用非自由程序,你就失去了至关重要的自由,所以永远不要这样做。

GNU 项目的目的是通过提供自由软件来取代那些践踏自由、主宰用户的非自由软件,从而使人们更容易拒绝它们。对于那些没有道德勇气拒绝非自由软件的人来说,这意味着一些实际的不便,我们尝试做的是提供一个自由的替代方案,以便你可以在较少的麻烦和较少的实际牺牲的情况下转向自由。牺牲越少越好。我们希望让你更容易地生活在自由中,进行合作。

这是一个合作自由的问题。我们习惯于将自由和社会合作视为对立面。但在这里,它们站在同一边。使用自由软件,你可以自由地与其他人合作,也可以自由地帮助自己。使用非自由软件,有人在支配你并让人们分裂。你不能与他们分享,你不能自由地合作或帮助社会,就像你不能自由地帮助自己一样。分裂和无助是使用非自由软件的用户的状态。

我们已经开发了大量的自由软件。我们做了人们说我们永远做不到的事情;我们有两个自由软件的操作系统。我们有很多应用程序,而且我们显然还有很长的路要走。所以我们需要你的帮助。我想请你为 GNU 项目做志愿者;帮助我们为更多的工作开发自由软件。请查看 gnu.org/help 以查找有关如何提供帮助的建议。如果你想订购东西,可以从主页链接到该页面。如果你想阅读哲学问题,请查看 /philosophy。如果你正在寻找要使用的自由软件,请查看 /directory,其中列出了大约 1900 个软件包(这只是所有自由软件的一小部分)。请编写更多的代码并为我们做出贡献。我的论文集《自由软件和自由社会》正在出售,可以在 www.gnu.org[9] 上购买。祝你黑客愉快!

脚注

  1. Guy Steele 设计了最初的对称 Emacs 命令集;然后我和他开始(在 TECO 之上)实现 Emacs,但在一次长时间的联合开发会议之后,Steele 开始逐渐疏远,所以我完成了 Emacs。其他人,特别是包括 Eugene C. Cicciarelli 和 Mike McMahon,在稍后做出了重大贡献。
  2. Bernie Greenberg 说 Dan Weinreb 为 Lisp 机器实现的 Emacs 早于 Greenberg 为 Multics 实现的 Emacs。我为这个错误道歉。
  3. 据我所知,Greenblatt 的计划是兼职雇佣实验室人员,以便他们可以继续在 AI 实验室工作。Symbolics 改为全职雇佣了他们,所以他们不再在 MIT 工作。
  4. 这个计划的背景,我没有在谈话中明确说明,是在最初的一段时间里,前 AI 实验室的黑客,无论是在 Symbolics 还是 LMI,都继续向 MIT Lisp 机器系统贡献他们的更改——即使合同没有要求这样做。Symbolics 的计划是单方面破坏这种合作。
  5. 我并不特别关心 LMI 的命运,而是我不想让 Symbolics 通过其对 AI 实验室的侵略而获利。
  6. 这句话被误解为我从来没有看过 Symbolics 的代码。实际上它说我一开始看过。Symbolics 的源代码在 MIT 可用,我有权在那里阅读它,而且一开始我就是通过这种方式了解他们的更改的。

    但这意味着我必须做出特别的努力来以不同的方式解决每个问题,以避免复制 Symbolics 的代码。过了一段时间,我认为最好甚至不要看。这样我就可以以任何最好的方式编写代码,而无需担心 Symbolics 的代码中可能有什么。

  7. Symbolics 曾经向 MIT 抗议说,我的工作通过阻止他们的计划,使 Symbolics 损失了一百万美元。
  8. 我不介意非常复杂和重量级的功能使用关键字参数。让我困扰的是让诸如“member”之类的简单基本功能使用它们。
  9. 在 2021 年,这本书可以从 GNU Press 购买。