我的Lisp经历和GNU Emacs的开发

(Richard Stallman在国际Lisp大会上的谈话记录,2002年10月28日)

由于我通常的演讲都和Lisp无关,我今天讲它们不合适。所以我将不得不即兴发挥。我的职业生涯做了太多Lisp相关的工作,因此我还是可以讲一些有趣的事情的。

我最早关于Lisp的经历是在高中时读了Lisp 1.5的手册。其中的思想让我脑洞大开:怎么还有这样的计算机语言!我第一次有机会做Lisp相关的工作是在当哈佛新生的时候,我为PDP-11处理器编写了一个Lisp解释器。那是一个非常小的机器—大概只有8k的内存—我设法用1000条以内的指令编写了那个解释器。这样我还有一点数据空间。这是在我看到真正做系统工作的软件之前。

当我在MIT开始工作时,我和JonL White一起真正开始实现Lisp语言。我不是被JonL,而是被Russ Noftsker招到人工智能实验室的,这一点最讽刺,想想接下来发生的事—他一定真的后悔有那一天了。

在1970年代,我的生活还没有被惊人的事件政治化之前,我只是不断地一个接一个地为各种程序添加扩展,其中多数和Lisp无关。不过,其间,我写了一个文本编辑器,叫Emacs。Emacs的有趣之处在于它有一个编程语言,用户的编辑命令可以用这个解释性语言编写,这样你就可以在编辑时加载新的编辑命令。你可以修改你正在用的编辑程序并且继续编辑。这样,我们就有了一个除了编程还可做其他有用之事的系统,而且你在使用时还可以对它进行编程。我不知道Emacs是不是第一个这样的工具,但是作为编辑器它确实是第一个。

这种构造庞大、复杂的程序作为自己的编辑器,然后再和其他人交换的精神正是我们当时在人工智能实验室拥有的、驱动自由合作精神的动力。它的思想就是你可以把你拥有的程序的拷贝分发给想要的人。我们和任何想要的人分享,它们是人类的知识。所以尽管我们分享软件的方式和设计Emacs之间并不存在有组织的政治关联,我仍然确信它们有联系,也许是不自觉的联系。我认为正是我们在人工智能实验室生活方式的属性导致了Emacs的诞生并使之发展。

最初的 Emacs 不带 Lisp。其底层语言、一个非解释性语言—是 PDP-10 汇编语言。我们编写的解释器实际上不是为 Emacs 写的,它是为 TECO 编写的。TECO 是我们的文本编辑器,也是一个极其丑陋的编程语言,能有多丑陋就有多丑陋。其原因是它本来就不是作为编程语言设计的,它是作为编辑器和命令语言设计的。比如,命令‘5l’意思是‘移动5行’,或者用‘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 功能。这些就是我们需要优化的部分。这项技术是我们已经在原始的 Emacs 上有意识地实践了的技术,因为我们用机器语言重新编写了某些相当上层的功能,并且把它们作为 TECO 的基本命令。比如,TECO 有一个填充段落的基本命令(实际上,是完成填充段落的大多数工作,因为其中一些不耗时的工作可以在上层由一个 TECO 程序来完成)。你可以编写一个 TECO 程序来完成整个任务,但是它太慢了,所以我们对其部分使用了机器语言做出优化。在此(指混合技术),编辑器的绝大部分是用 Lisp 编写的,但是那些需要非常快速运行的部分是用底层语言写的。

因此,当我编写第二版Emacs时,我采用了同样的设计。底层的语言不再是机器语言,而是C。就编写运行在类Unix系统上的可移植程序来说,C是一个优秀的、高效的语言。虽然有一个Lisp解释器,但是我直接用C实现了一些特定的编辑功能—管理编辑器的缓存、插入起始文本、读写文件、刷新屏幕显示以及管理编辑窗口。

当时,它不再是第一个用 C 编写并运行在 Unix 上的 Emacs 了。第一个是由 James Gosling 完成的,就是 GosMacs。他有些奇怪。一开始,他看来还是受到了原始 Emacs 的合作和分享精神的影响。我首先在 MIT 向人们发布了 Emacs。有人想要把它移植到 Twenex 系统—它最初只运行在我们在 MIT 使用的不兼容分时系统上。人们把它移植到了 Twenex 上,这意味着世界上可能有数百个设备可以安装该 Emacs。我们开始发布该版本,发行遵循的是“你必须发回所有的改进”这样大家都受益。没有人会刻意强调这个规则,但是就我所知人们都是合作的。

Gosling一开始,的确看起来是以这样的精神参与的。他在一个使用手册里称此程序为Emacs,希望社区能够改善之,使之配得上这个名字。这是参与社区的正确方式—请大家参与并使程序更好。但是在此之后,他似乎改变了态度,并把程序卖给了一个公司。

那时,我正在为GNU系统而忙碌(一个类似Unix的自由软件操作系统,许多人错误地称之为“Linux”)。那时并没有跑在Unix上的自由软件版的Emacs编辑器。不过,我有一个参与了Gosling的Emacs开发的朋友。Gosling通过邮件给予他发布自己版本的许可。他建议我使用他的版本。然后,我发现Gosling的Emacs带的不是真的Lisp。它带的编程语言是‘mocklisp’,其语法看起来像是Lisp,但是没有Lisp的数据结构。所以其程序不是数据,而且也缺失Lisp的重要元素。其数据结构是字符串、数字和其他一些专门结构。

我得出我不能使用该程序并且要完全替换该程序的结论,第一步就是编写一个真正的Lisp解释器。我逐步用真正的Lisp数据结构替换了该编辑器的每个部分,而不是使用其专门的数据结构,并使编辑器的内部数据结构对用户的Lisp程序开放,使用户程序能够处理编辑器内部数据。

显示刷新是个例外。很长时间以来,显示刷新像是一个另类世界。编辑器一旦进入显示刷新的世界,事情就变成对垃圾数据收集不安全的非常特殊的数据结构,它们对中断处理也不安全,而且你在此过程中无法运行任何Lisp程序。我们已经修改了这部分—现在你在显示刷新时也可以运行Lisp代码。这是一件很便利的事。

这个第二版的 Emacs 是现代意义上的‘自由软件’——它是力争软件自由的政治运动的一部分。该运动的精髓就是每个人都应该有自由做我们在 MIT 的那段岁月做的事,一起开发软件,只要愿意就可以一起工作。这就是自由软件的基础——也是我的经历,是我在 MIT 人工智能实验室的生活——为人类的知识而工作,而不是阻碍其他人使用和传播人类的知识。

那时,你可以用和造非 Lisp 专用机差不多的价钱造一台电脑,它运行 Lisp 的速度要快得多,而且对每个指令都做类型检查。普通电脑一般是让你在运行速度和类型检查之间选一个。所以,是的,你可能使用Lisp编译器让你的程序运行很快,但是如果你对数字做 car1 时,结果是无效的,最终导致程序崩溃。

该 Lisp 电脑运行指令的速度几乎和其他电脑一样快,但是它对每个指令——car 指令都会做数据类型检查——所以当你在编译过的程序里试图用 car 指令处理数字时,系统会立即报错。我们制造了这种电脑并且有一个专门的 Lisp 操作系统。该系统几乎完全是用 Lisp 编写的,只有一部分是用 microcode 编写的。人们对大规模造这种机器很感兴趣,这意味着他们应该开个公司。

对开什么样的公司,大家有两种不同的想法。Greenblatt想开一个他称之为“黑客”的公司。这意味着该公司会由黑客运作并且以有利于黑客的方式运作。另一个目的是要保持人工智能实验室的文化(3)。不幸的是,Greenblatt没有任何商业经验,所以Lisp电脑团队的其他人对Greenblatt会不会成功表示怀疑。他们认为他拒绝外部资金的做法行不通。

他为什么要避免外部投资呢?因为如果一个公司引入了外部投资者,他们就会掌控公司并且不会让你有任何道德上的犹豫。最终,如果你稍有犹豫,他们也会踢开你。

所以,Greenblatt认为他可以找到预先付款的客户。他们再造电脑发送给该客户;有了这些利润之后,他们就可以再造更多的电脑,然后再销售,然后再造更多的电脑,如此进行下去。组内的其他人觉得这样不行。

然后,Greenblatt就雇佣了Russell Noftsker,就是招我进来的人,他后来离开了人工智能实验室并开创了一个成功的公司。Russell被认为很有商业头脑。他向组内的其他人展示了他的商业才智,他说:“我们抛开Greenblatt,不要管他的想法,我们去开另一间公司。”背后捅刀子,明显是真正的生意人。这些人决定开一个叫Symbolics的公司。他们吸纳外部投资、不会有任何良心不安、竭尽所能获取成功。

但是Greenblatt并没有放弃。他和少数忠诚于他的人还是决定开办了Lisp Machines公司,并按自己的计划前进。你猜怎么着,他们成功了!他们找到了一个预先付款的客户。他们制造了电脑并卖了出去,并制造了越来越多的电脑。尽管没有组内多数人的支持,他们实际上还是成功了。Symbolics也成功地启动了,所以你有两家互相竞争的Lisp电脑公司。当Symbolics看到LMI不是走向失败时,他们开始寻机搞破坏。

因此,实验室的被离弃接着是实验室的“战争”。离弃是因为 Symbolics 挖走了实验室的所有黑客,只剩下我和几个在 LMI 兼职的人。然后,他们又援引条例把在 MIT 兼职的人赶走,因此他们全都走了,只剩下我。人工智能实验室现在没什么希望了。而且 MIT 对这两个公司做了非常愚蠢的安排。这是一个三方合同,其中两家公司都被授权使用 Lisp 电脑系统的源代码。这两家公司都需要让 MIT 使用他们的更改。但是合同没有说 MIT 有权将这些代码放入由两家公司授权的 MIT Lisp 电脑系统。没有人预见到实验室的黑客小组会被彻底毁灭,但是它就是被毁灭了。

于是,Symbolics 启动了一个计划(4)。他们对实验室说,“我们会持续让你们使用我们的系统改进,但是你们不能把这些改进装到 MIT 的 Lisp 电脑系统上。不过,我们会让你们使用 Symbolics 的 Lisp 电脑系统,你们可以在这些系统上运行改进版,但是仅此而已。”

这实际上是要求我们只能选择一边,要么使用 MIT 的系统版本,要么使用 Symbolics 的版本。我们选择哪一边,就决定了我们的系统改进会进入到哪一边。如果我们为改进 Symbolics 的版本而工作,我们就只是在帮助 Symbolics。如果我们使用和改进 MIT 的系统版本,两个公司都能够得到我们的工作,但是 Symbolics 认为这是在支持 LMI,因为这会帮助 LMI 生存下去。因此,我们不被允许再保持中立。

直到此时,我都没有站在其中任何一个公司的一边,虽然我看到社区和软件发生的一切感到非常难受。但是现在,Symbolics强迫我做出选择。所以,为了使Lisp Machines公司能够继续(5)—我开始复制Symbolics对Lisp电脑系统所作的全部改进。我把这些改进用自己的想法同样实现出来(就是说,代码是我自己写的)。

过了一阵,(6),我得出结论,不看他们的代码可能更好。当他们宣布beta版时,通过看发布声明,我知道了有什么功能,然后自己实现它们。当他们发行正式版时,我也实现了这些功能。

如此这样,有两年的时间,我阻止他们把Lisp Machines公司消灭;这两个公司同时存在。但是,我不想这样年复一年地惩罚一个人,仅仅只是阻挠一桩罪恶。我觉得他们已经受到了彻底的惩罚,因为他们陷入了无法摆脱的竞争漩涡之中(7)。与此同时,是时候再开始创建一个新的社区来代替那个被他们以及其他人毁灭的社区了。

70 年代的 Lisp 社区不限于 MIT 人工智能实验室,并不是所有的黑客都在 MIT。Symbolics 发起的战争毁灭了 MIT 社区,但是同时还有其他事件在进行。有人放弃了合作,这些也毁灭着社区,社区所剩寥寥了。

一旦我不再惩罚Symbolics,我就不得不考虑下一步做什么。我必须做一个自由的操作系统,这很明显—人们能够一起工作和分享的方法就是有一个自由的操作系统。

一开始,我想要做一个基于Lisp的系统,但是我认识到那在技术上并不一个好主意。要做像Lisp电脑那样的系统,你需要专用的微代码。这种微代码使你能够和其他电脑一样快速地执行程序,同时还能获益于类型检查。没有微代码,你就只相当于其他机器上的Lisp编译器。程序可以更快,但是并不安全。如果你在一个分时系统上运行一个这样的程序还凑合—一个程序崩溃并不是灾难,用户程序时不时地都可能会崩溃。但是这样编写操作系统就不行,所以我抛弃了做类似Lisp电脑系统的想法。

我决定做一个类似Unix的操作系统,可能会带一个能够运行用户程序的Lisp环境。内核不必是用Lisp编写的,但是我们应当有Lisp。所以正是这个操作系统、GNU操作系统的开发指引我编写了GNU Emacs。在编写的过程中,我的目的是做一个尽可能最小的Lisp系统。该程序的大小是非常重要的考量。

在1985年,有些人电脑的内存是不带虚拟内存的1兆字节。他们也想要运行GNU Emacs。这意味着我必须使该程序尽可能地小。

举个例子,当时的循环结构只有‘while’,它简单到极致。你无法直接跳出‘while’循环,你只能进行一次异常捕获(catch)和一次异常抛出(throw),或者判断控制循环的变量。这个例子说明我为了使程序变小,做出了什么样的努力。我们也没有‘caar’和‘cadr’等等指令;“尽可能减少不必要的东西”是GNU Emacs的精髓,是Emacs Lisp的精髓,从一开始就是这样。

当然,现在的电脑大了,我们也不再追求那样的极致。我们加入了‘caar’和‘cadr’等指令,而且我们最近也会添加另外的循环结构。现在我们愿意扩展它,但是我们不想把它扩展成common Lisp那样。我曾在Lisp电脑上实现过Common Lisp,我对之并不是十分满意。其中我非常不喜欢的就是关键字参数(8)。在我看来,它们不算Lispy;虽然我有时也用关键字,但是我把我用的次数控制到最小。

这并不是GNU工程涉及Lisp的结束。后来在1995年左右,我们计划启动一个图形化桌面项目。我们很清楚该桌面程序的主要编程语言应该能够便利地扩展该桌面程序,就像我们的编辑器一样。问题在于这应该是一个什么语言。

此时,TCL 作为候选语言的呼声很高。我非常看不上 TCL,基本上因为它不是 Lisp 语言。它稍稍有点像 Lisp,但是其语义上不是,而且也不简洁。然后,有人给我看了一个广告,其中说 Sun 公司正试图雇人把 TCL 变成全世界“扩展语言的实质标准”。我在想,“我们必须阻止这件事”,所以我们开始让 Scheme 成为 GNU 的标准扩展语言。不是 Common Lisp,因为它太庞大了。其思想是,我们把 Scheme 解释器连接到应用程序中,就像 TCL 那样。然后,我们就可以建议这是所有 GNU 程序首选的扩展包。

使用这个强大的 Lisp 分支语言作为基本扩展语言会给你带来一个有趣的好处。你将能够把其他语言翻译成基本扩展语言来实现其他语言。如果你的基本语言是 TCL,那么你就不能够轻易地用翻译成 TCL 来实现 Lisp。但是如果你的基本语言是 Lisp,那么翻译就不是难事。我们的想法是,如果每个扩展应用都支持 Scheme,那么你可以用 Scheme 实现 TCL 或 Python 或 Perl,用来把应用翻译成 Scheme。之后,你就可以把它加载到每个应用,然后就可以用自己最喜爱的语言来定制应用了,其他定制也类似。

如果扩展语言很弱,用户就不得不使用你提供的唯一语言。这就意味着,喜欢特定语言的人们将不得不和开发者选择的语言竞争—他们会说“应用开发者,请把我的语言添加到你的应用中,而不是添加别人的。”这样,用户就根本没有选择的余地—他们只能接受应用带来的语言,并受制于[该语言]。但是如果你有一个强大的语言,它能够实现其他语言,那么你就给了用户自由选择语言的权利,我们就不再有语言选择的战争了。这正是我们所希望的‘Guile’,我们的scheme解释器,能够做到这一切。去年夏天,我们有人完成了从Python到Scheme的翻译器。我不太确定这个是否已经全部完成,不过如果有人对这个项目感兴趣,请联系我们。这就是我们未来的计划。

我一直没有提自由软件,不过现在让我简单说一下它的意义。自由软件指的不是价格;它的意思不是你可以免费得到的软件。(它可以是你付费得到的软件,也可以是你免费获得的拷贝。)它的意思是作为用户,你有一些自由。其中的关键是你有自由运行该软件、你有自由研究其所作所为、你有自由按照自己的需求修改该软件、你有自由向其他人发布该软件以及发布修改后的软件。这就是自由软件的定义。如果你使用的是非自由软件,你就失去了这些关键的自由,所以请不要使用非自由软件。

GNU工程的目的就是提供代替非自由软件的自由软件,让人们能够更容易地拒绝非自由软件对自由的侵害、对用户的控制。对那些缺少道德勇气来拒绝非自由软件的人来说,如果你需要的是实用性,那么我们将尽力提供一个自由的替代使你能够在较少混乱、较少牺牲实用性的情况下,转移到自由软件。你失去的实用性当然是越少越好。我们希望你能够更容易地获得自由、并且能够合作互助。

合作互助关乎自由。人们习惯于认为自由和社会合作是对立的。但是,此时我们在同一条战壕内。使用自由软件,你就有自由和其他人合作,并且也有自由帮助你自己。使用非自由软件,你就被控制,人们被分化。你无权和其他人分享,你没有自由去合作互助或帮助社会,正如你没有自由来帮助你自己。使用非自由软件的用户就是这样孤立和无助。

我们已经开发了范围庞大的自由软件。我们完成了人们曾经说永远完不成的事;我们有两个自由软件操作系统。我们有很多应用,但我们显然还有更多的要做。所以,我们需要你们的帮助。我请求你们成为GNU工程的志愿者;帮助我们开发能执行更多任务的自由软件。请参看http://www.gnu.org/help来了解怎么帮助我们。如果你想订购一些东西,我们的主页上有一个链接。如果你想了解我们的哲学,请阅读/philosophy。如果你在找可用的自由软件,请到/directory,现在大约有1900个软件包(这只是所有自由软件的一部分)。请编写更多的自由软件并贡献给我们。我的文集,“自由软件和自由社会”,正在发售,你可以在www.gnu.org购买。祝你们开发愉快!

  1. Guy Steele设计了Emacs最初的对称命令集合;然后我们开始构造Emacs(在TECO的基础上),但是在一段长期的联合开发之后,Steele渐渐离开了,所以我独自完成了Emacs。其他人,特别是Eugene C. Cicciarelli和Mike McMahon后来也做出了显著的贡献。
  2. Bernie Greenberg说Dan Weinreb在Lisp电脑上的Emacs要早于Greenberg在Multics的版本。我在此对我的错误道歉。
  3. Greenblatt的计划,就我的理解,是请实验室的人员兼职,这样他们还能够继续在人工智能实验室的本职工作。Symbolics采取的是全职雇佣,所以实验室的人员就不能在MIT工作了。
  4. 该计划的背景,我在演讲里没有明说,是在开始阶段,人工智能实验室的黑客们,不管是在Symbolics,还是在LMI,都继续把他们的改进贡献给MIT的Lisp电脑系统—尽管合同并没有要求这样做。Symbolics的计划却是单方面割裂合作。
  5. 这并不是说我特别关注LMI的命运,我只是不想让Symbolics通过攻击人工智能实验室获益。
  6. 这个陈述常被曲解为我从来没有看过Symbolics的代码。实际上,它说的是我一开始看了Symbolics的代码。这些代码就在MIT,我有权看的,而且最初我就是这样了解到他们做了改动。

    不过,这意味着我不得不花力气找到其他的解决方法,这样才能避免复制Symbolics的代码。一段时间之后,我觉得不看这些代码更好。这样的话,我就可以按最好的方法来实现代码,而不用考虑是否用了Symbolics的代码。

  7. Symbolics曾经向MIT抗议:我的工作阻挠了他们的计划而使Symbolics损失一百万美元。
  8. 我并不介意非常复杂和重量级的函数使用关键字参数。我讨厌的是连诸如“member”这样的简单函数也要使用关键字参数。

译注

  1. car:是Lisp语言的一个基本指令。