GNU Emacs 体系架构评论

来源:互联网 发布:不实名域名注册商 编辑:程序博客网 时间:2024/05/01 00:34

转载至http://zhan.renren.com/iemacs?tagId=7957&from=template&checked=true


GNU Emacs 是自由软件协会的旗舰产品,也是自由软件之父 Richard Stallman博士的得意之作。自十几年前问世以来,其基本体系架构一直保持稳定,人们在这个基础架构之上不断添砖加瓦,逐渐将 GNU Emacs 构建成一个壮观的软件系统。    用户越来越多,功能越来越强,又没有专有软件开发所特有的集中的管理,GNU Emacs的基本体系却能长期保持稳定不变,这在信奉“唯一不变的就是变化本身”的十倍速时代,几乎可以说是一个传奇。究竟GNU Emacs是依靠什么秘方常盛不衰?
恐怕只有对自由软件及GNU Emacs 有深入了解的人才能为我们揭开。 为此我采访了洪峰先生。


关心中国自由软件事业的朋友都应该知道洪峰先生。洪先生不仅是中国自由软件事业的主要推动者和领导者,还是一名真正的黑客。在他身上,黑客的理想主义气质和实干家的魄力与务实精彩地融合在一起。这篇访谈以技术探讨开始,涉及了很多内容。相信不论是从技术上还是从思想上,洪先生在这篇谈话中表露出来的东西都能够给我们的读者带来更深刻的思考。


孟: 洪峰老师,您好!我手上有一本《GNU Emacs Lisp编程入门》,您是这本书的审校,还为它写了“译者序”。您的序言里有这么一段:“斯托曼的一个天才构想就是利用C 语言编写与硬件直接作用的GNU Emacs模块(如显示模块),而绝大多数文本编辑模块则统统是利用Lisp语言来编写。..这
一几乎无限的灵活性是其他编辑器很难做到的。在GNU Emacs 中,Emacs 的Lisp代码模块和C 代码模块组织良好,它们互相取长补短,相得益彰。..斯托曼的这一天才的泛对称设计思想极富艺术性,具有方法论研究的永久价值。” 你这段话其实讲的是软件的体系设计思想。不过在序言中似乎您欲言又止,没能展开谈,所以特别希望您能就这个问题着重讲一讲。我们先从Emacs 谈起吧!从程序设计者的角度,您怎么描述Emacs 的总体结构?您认为它属于层次结构还是组件结构,或者是一种独创的结构?

洪:首先,我想告诉你,历史上出现过很多Emacs的版本,GNU Emacs 是其中的一个,也是影响最大的一个。因此我们的谈话就集中在GNU Emacs 上面。下面如果不特别说明,我说的Emacs 就是指GNU Emacs。Emacs 采用的体系结构是自底向上的层次结构,大体上分成两层:底层的C 代
码(被封装了),高层的Emacs Lisp 代码(是开放的)。这个结构是RMS 独创的。


孟:您说底层的C 代码是被封装了,而上层的Emacs Lisp 是开放的。我有一个问题,为什么开放的上层不使用C来构造呢?那样不是更直接,而且执行效率更高吗?

洪:注意C 和Lisp 两者的区别:C 是编译型的强类型的语言,这意味着它的代码必须以二进制格式运行,特点是运行效率高。但是Emacs 上层的编辑工作主要的数据是文本,而文本的本质就是Lisp 中的符号列表,当然也可以使用C 代码来处理文本,但是如果你想对操作文本的C 代码进行改动时必须按照“ 编辑-编译- 链接-调试”循环的步骤来改。对于一个功能强大的编辑器,这是不可忍受的。

一个例子是使用 C 语言编写的 Vim,它的运行速度很快,但是一点也不灵活,谈不上有什么扩展性。(这里VIM的爱好者应该会有话说的吧:)

注意,Emacs Lisp 的代码有两种形式,一种是纯文本形式存在的.el 文件,还有一种是编译过的.elc 文件,这是一种字节码格式的文件,运行起来速度很快。(Java的字节码的思想就来自于Lisp。),因此一些经过时间检验的Emacs Lisp代码可以使用字节码方式运行,大大提高速度,而其他的Emacs Lisp 代码就使用解释方式运行,以求得扩展性。 当 Emacs Lisp 以解释方式运行时,它不需要C 那种“编辑-编译-链接-调试”的步骤,而是“阅读-求值-打印”的步骤,也就是说,新设计的程序的效果马上就可以看到,结合Emacs 编辑工作,你的编辑功能马上就可以生效并投入使用。


孟:仅仅是下层用C,上层用Lisp,也并不一定就可以产生强健而灵活的系统吧!我特别关心一个问题,两层之间如何结合,如何相互作用?这个问题设计不好,会适得其反。GNU Emacs在两个层次结合上肯定有特别卓越的设计,您能分析分析吗?


洪:Lisp语言有一个强大的功能就是支持宏的运算。宏在Lisp 中是用户自定义的特殊表,按照C 程序员的思路理解,宏就是用户自定义函数。对于宏,Lisp 处理时分成两步完成,先展开,再求值。因此,Lisp 可以使用宏来模仿C。 C 函数肯定需要零个或者多个参数,因此Lisp可以利用宏将Lisp 代码变成C 函数可以理解的参数(即展开),然后将控制交给C 程序(做求值),C 计算完成后,再将结果返回给Lisp,回到编辑状态。《Introduction to Emacs Lisp Programming》书中就给出了一个实际的例子,你可以仔细研究一下。


孟:您在序言中提到的“泛对称”是什么?您觉得Emacs 设计的美感体现在哪些方面?您认为这种美感是可以模仿的吗?


洪:泛对称是一种广义的对称性,而对称性是科学的基本美感的组成要素之一。Emacs 的美感主要集中体现在“混合编程”(hybrid programming)上,底层C代码的健壮和高层Emacs Lisp 代码的灵活性与可扩展性,两者完美地结合在Emacs这个程序身上,这使得Emacs 具有一种和谐的美,这种和谐是Emacs 强壮而又灵活的根源。我们不能要求所有的软件都具备这样的性质,但是这种追求美的素质却是每一位程序员都要培养和追求的。孟德斯鸠说,当一个令人愉快的东西我们还没有发现它的实际价值时,我们可以认为它是美的。我看这句话比较适合中国的大多数程序员:他们听说过Emacs,但是尚不知道它的实际价值。


孟:我想冒昧地问一句,RMS选择Lisp ,究竟是因为Lisp 真的很优秀,还是因为他对于Lisp 的熟悉?或者说,如果RMS 在今天开发GNU Emacs ,他还会选择Lisp 吗?会不会选择Python,Perl 或者Java ? Lisp 的那些特质使得它更适合

上层开发?

洪:两种原因都有。我猜想,如果RMS 今天再编写Emacs,他一定会直接使用Guile(也就是Scheme)来写那些Emacs Lisp 代码。绝对不可能使用 Perl,Java,也不太可能用Python。因为这些编程语言尽管都有与C 的接口,但是全部都缺乏Lisp 独有的元语言的特性(元语言特性?)


孟:再说说下层。如果是在今天,您认为RMS还会使用C 来开发Emacs 的下层吗?会不会用C++ 或者Objective-C 等面向对象语言?为什么?洪:对于Emacs 的设计,他一定仍会使用C,而不会使用C++。RMS 本人并不喜欢C++,因为它太复杂,而且没有改变C的一些固有问题。如果强制他一定不能使
用C,那么他有可能会采用Objective-C,因为Objective-C比C++简单多了。C++是我见到的最复杂的编程语言之一,对于初学者,它的门槛实在很高。


孟:现在的软件体系结构大致有单体结构,层次结构和组件结构。如果让您把层次结构和组件结构做一个比较,您认为孰优孰劣?两者可否结合起来?您觉得GNU Emacs 的经验对此有何启示?


洪:我一般使用函数式编程、命令式编程、面向对象编程这些术语。不要把这两者对立起来!应该根据要设计的软件本身的特点来选择软件的结构,而不是先假定好一个结构,然后费力地让软件的代码去适应这个结构。这就是毛主席曾经教导我们说的:“上什么山,唱什么歌。”


孟:是否可以认为类似Java和.NET这些新近的软件体系结构也受到了Emacs 的影响?站在 Emacs 的观点看待COM ,Java,.NET 这些技术,您有何评价?


洪:微软的COM 和C #技术都是专有技术的产物,因此我不打算在这里评论它们。 至于Java,它的设计师之一 James Goslin 曾经是Lisp 社团里的一个活跃份子,他用C写过一个Emacs,Java的设计思想里到处可以看到Lisp 的影响。刚才我已经提到了一个例子:字节码。Java程序是分两步走的,先编译为字节码,然后在虚拟机上解释地执行字节码。这不是Java的首创,Lisp早在50年前就有了,只是Java 走得更远一些,把一种计算机内使用的字节码格式扩展到了所有的机器上,实现它“编写一次、到处运行”的目的。再举出一个例子:元数据。Java的字节码文件以.class结尾,而在这个文件中含有对文件自身的说明。元数据的概念起源于Lisp,因为在Lisp中,程序和数据是以相同的形式表达的,程序可以当作数据处理,数据也可以编成程序进行运算。这一点C程序员可能难于理解,怎么程序可以当作数据来处理呢?但在Lisp中,两者在形式上的确是一致的,都是使用符号表达式,你可能已经注意到了Lisp程序中的一对对小括号,它们就是用来标明符号表达式的。我还可以举出一个例子:内存垃圾收集。Java中有内存垃圾自动收集机制,而这个机制是Lisp首先引入并实现的。


孟:GNU Emacs的解释内核跟现在流行的虚拟机有什么相同和不同之处?


洪:Emacs 的解释器是采用Emacs Lisp 编程语言编写的,它是编辑任务的执行环境,某种意义上说,这个解释器的确就是一个虚拟机。不过Emacs Lisp这个虚拟机与以往的Lisp 语言实现版本有一个巨大的区别:以往的Lisp实现版本都是自含式的系统,而Emacs Lisp则是可以充分利用操作系统的功能的,例如敲入M-xhell 这个命令,你就可以在Emacs 中直接运行操作系统的shell,而不用退出
Emacs。你可以在Emacs 环境中收发电子邮件,排版,浏览网页等等。


孟:我读那本elisp的书,知道了我在Emacs中的每一次操作,甚至每一次按键实际上都是在执行一个对应的表达式。这个表达式可能是elisp程序,也可能实际上是一个C程序。用户的操作是通过某种机制与某个表达式绑定在一起的。我们可以改变这种绑定,从而让同一个操作引发不同的表达式,表现出不同的效果。请问我的上述理解是否正确?如果是这样的,那么我们是不是可以说E m a c s 内部也具有一种独特的Event-handler 架构?


洪:你的理解有一部分是对的,有一部分不准确。对的部分是:在用Emacs编辑时,你下的命令,例如 Mx ispell-region将被绑定到一个程序上,这个程序就是shell 下也可以运行的ispell,然后程序的控制交给ispell 执行。计算完成之后,再回到Emacs 的编辑状态。不准确的部分是:Emacs 内部没有你说的Event-Handler 架构,一切都是阅读-求值-打印的循环,特别简单。实际上,任何编程语言的最低层,都是有这么一个机制的,毕竟无论什么计算,最终都是由CPU 的ALU 完成的算术运算或者逻辑运算。但是Lisp 可以直接与这些机器指令交互(Lisp/Scheme 可以直接操作CPU的寄存器)。换句话说,任何编程语言都有Lisp的计算模型在里面,可能绝大多数程序员从来就没有意识到这点。从这个意义上说,Lisp 的确是一种元语言。


孟:我长期从事Win32 开发,对于Windows 中的事件做过不少思考。我认为在一个事件驱动的系统里,有三个关键问题决定了整个系统的表现。我先说第一个,事件消息表示和传递机制。比如Win32,把所有的事件表示为一个unsigned int,附加以两个4字节整数作为参数(wParam, lParam),所有的事件消息,都无一例外地被强行规范化为这样的消息格式,发送给相应的窗口对象。这样做当然是为了满足C这种静态语言的条件,但是带来的缺点是每个事件消息本身的信息很少,必须有与之相配合的一系列外部协议,而且这些协议没什么规律,只能通过文档的形式来申明。所以在Win32 编程中,没有文档参考是不可想象的,即使有很多年的Win32 编程经验,也需要不厌其烦地去查文档。我曾经想,如果能够发送格式自由的消息,再规定一个消息含义的规范化语言(类似printf 中的那种little language),那么不需要很多文档,也可以很轻松的发送消息了。现在的SOAP 协议,实际上是通过XML 在网络上实现了这种消息协议。


洪:首先我应该告诉你,Win32 的模型与X Window的模型在本质是相似的,都是事件驱动机制。而且我还应该告诉你,最早实现的X Window系统的一个库是用Lisp 编写的,称为CLX,至今还有一个X视窗管理系统的发行版 Sawfish,使用的就是Scheme,但是它设计得不太好。 你提到的这一点很有道理。 Win32与DOS 模型相比,当然是一个巨大的进步,但是它没有彻底解决问题,因为一旦事件发生
后,被视窗的事件循环检测机制捕捉到后,必须有事先设计好了的一套动作使用一个句柄来与之配套,事件才能往下进行处理。所以,有多少个应用程序,就会有多少种事件,就需要有多少种句柄事先就要设计好来与之匹配,这是一个永远也做不完的机制,按照陈榕博士的说法,Win32 是一个半成品。更加糟糕的是,Win32 API 底层的实现机制是不公开的,因此对于第三方开发人员而言,他们一
旦选择了Win32,就等于“卖身”给了微软。


Lisp/Scheme 的思想是,符号与数据对象的绑定是任意的,就像一个人可以使用多个化名一样,你可以将任意多个符号绑定到一个对象上,然后将符号放在列表中进行操作,而列表的格式只有一种。你可以在不同的块结构中做不同的绑定,因此它提供了巨大的灵活性。 Scheme 一个比C++ 设计得好的方面是,Scheme的符号本身就是隐含地具有名称、值、类型、属性表和绑定函数表五个特征,其中类型是动态类型,根据运行时的环境而决定,Lisp/Scheme 天生就是一个面向对象的语言。C++费了牛劲才在C的基础上得到了对多态性的支持,但是Lisp 早在50 年代就已经实现了。不同的是C++ 的虚函数表机制是基于stack 的,而Lisp是基于heap 机制的,stack 是一个“准紧凑”的数据结构,heap 中的数据则不一定都是LIFO 结构,内存垃圾自动收集还要损失一些速度,因此早期的Lisp 的效率与C++ 相比不算高,但是我上次讲过了,目前的硬件物质基础已经跟上来了,因此经过优化设计的Scheme 程序以编译方式运行时是完全有可能在速度上超过C++ 程序的。Java 程序在速度上根本不是Scheme 的对手,www.schemers.org 上有一篇文章,对比Scheme 与Java 的性能,你可以研究一下。


孟:Emacs中的消息——表达式绑定给我提供了一种新的思路。您认为这种机制相对于上面提到的机制有何优势? 在单机系统和网络中是否都能够适用?有没有这方面的研究项目?


洪:平行处理能力越强的计算机,运行Lisp程序的性能越优越,日本的 NEC 在支持一项研究,技术方向就是这个样子。从根本上说,函数计算需要非冯诺伊曼体系的支持,效率才会有大的突破,原来的方向是通过专用的芯片来搞,而目前的集群技术为在大陆货的硬件上得到平行计算技术的效果带来了新的思路和希望。


孟:好的。我再说第二点:可以处理的消息数量。这主要涉及到类似MFC 的Win32 C++ wrapper。在C++ 中,上面所说的问题在名义上得到了解决。因为成员函数的参数表可以由我们自己设计。但是在C++中一个对象在运行时能够处理什么消息,完全是编译时静态决定的。给一个对象发送它并不打算处理的消息,这样的行为在编译时就会被禁止。表面上这维护了系统的类型安全性,但是实际上并不符合事件驱动的交互式系统对象模型。在这样的系统中,一个对象可能需要处理的消息实际上是无限多的,只不过实际上它只关心其中的一部分,其他大部分它不关心的消息它只是简单地传递给一个公共处理器(DefWindowProc)来处理。可是在C++中,由于所有的消息都必须事先确定下来,系统一旦定性,就成为刚性的框架,扩展起来很困难,所以设计者必须有卓越的眼光和超前的思维,预料到可能发生的各种变化,这实际上是不可能的。因此,我认为C++对象模型与事件系统对象模型本质上是不一致的。Emacs 借助了Lisp的动态特性,在这方面自然具有先天优势。不过我对于Emacs内部还不了解,Emacs 内部也有很多“对象”吗?您认为Emacs 的结构如果扩展整个应用系统甚至操作系统,依然是优越的吗?请在这方面谈谈您的看法。


洪:你的理解是对的。关键的原因在于C++想保持与 C 的兼容,它不肯放弃对stack 运行时环境的支持(如果放弃了这点的话,它就不是C++了。)这样做的优点是程序比较紧凑,适合实现过程或者机制,但是在算法(策略)的灵活性上,它根本没有办法与Scheme 相比。你再结合我们的时代看看,就会同意这样的观点:硬件的成本总是年趋下降的,而人的成本总是在上涨的,因此编程语言必须具有灵活性和良好的扩展性,使人可以在相对较短的时间内拿出可以搞定问题的算法来,至于程序运算起来的效率如何,则由机器去搞定。应该说它仍是一个重要的因素,但是重要性与以往相比,则大大下降了。而且一旦某些地方对速度要求很严,就可以将这部分的代码采用基于stack 的原子函数封装掉。Emacs Lisp内部的确有很多对象,具体可以参考Emacs Lisp的参考手册,对象的类型是在运行时动态决定的,也就是说,Lisp 天生就是支持多态性。将Lisp扩展到整个操作系统时,还有很多其他的因素要考虑,例如对于进程调度、内存管理、设备的管理等,这些东西在MING 系统中都是原子函数。所以,如果一个应用程序要申请使用设备,那么就把设备驱动的原子函数加入到程序的参数列表中就行了,至于程序的参数列表如何与设备驱动去绑定,则交给运行时系统去决定和完成。当然,这里有大量的中断、优先级别、权限等等策略问题,这些原子函数在算法空间里要由另外的原子函数完成聚类和离散操作(以得到计数和排序),其细节是不可避免地相当复杂的。我在国内讲了很多次关于MING的设计思想,好像迄今只有陈榕一个人真正理解我的意思。首先是一些教授和学者一听Scheme,就不愿意向下听了,也不知他们是真的理解Scheme(从而认为这条方案不行),还是听不懂。


孟:说实在的,第一次听到您说用解释语言来写OS,我也很诧异和怀疑。以后有机会我还要为这向您请教呢。现在我们再来看第三点,消息传递路径。上面的问题中说到,在GUI系统中,一个对象不关心的消息可以传给DefWindowProc,这就构成了一个非常简单的消息传递路径。MFC为了模仿Windows 事件机制也模拟了自己的消息传递路径。德国人Miro Samek把这种模式叫做“Ultimate Hook”,并且认为具体窗口对象与Windows 系统之间形成了一种inheritance关系,只不过既不是interface inheritance,也不是implementation inheritance,而是behavior inheritance。 我想知道Emacs 中是否存在这种消息传递?如果从Lisp 和Scheme 出发,这种所谓的消息传递路径是否有独具风格的解决之道?


洪:我的理解是,当计算中需要很多指针传递时,最好是将多级指针通过符号与对象的重新绑定给“短路”掉效率最高。在C++ 中,你知道,每一次间址,都引发了更多的(设计上的)复杂性,而且每次间址运算或者函数调用,在内存空间里都不可避免地引起从一个栈到另一栈的跳转,并且最终还要返回,这一切最终都变成了程序员的负担。 (Scheme 要求解释器的原子历程和库函数一律实现尾递
归。) 你说的这种消息路径在S c h e m e 中称为计算的连续性,Scheme 可以在内存空间中无限制地跳转,从而得到上面的“短路”效果,它是编程语言王国中的一等公民,其他都是二等、三等公民。如果你现在一下子不理解,没有关系,过了一段时间,你就会明白的。编程和计算科学的美感就集中体现在这些地方。但是遗憾的是,目前国内没有一所大学里,开设了Scheme 课程,从而使所有的学生都丧失掉了体会这种美感的机会。


孟:您反复提到一种叫Scheme 的语言,能不能给我们介绍一下?


洪:Lisp 有很多变种,Emacs Lisp 只是其中一个。Scheme 也是Lisp 的一个变种,诞生于1975 年。它与它以前的Lisp 和以后的Lisp 变种都不同。除了Scheme 之外(如果你不算 Emacs Lisp),那么所有的Lisp 现在都已经统一在Common Lisp 中了。

RMS 说过,Emacs Lisp 将来会被 Scheme 逐步替换掉。替换Emacs Lisp 的Scheme 称为Guile。(顺便告诉你,在RMS 的头脑中,Guile 将是GNU 系统上默认的脚本语言)。与以往的Lisp相比,Scheme最大的变化就是引入了块结构和词法定界两个特点。这两个特点大大地方便了程序员编写程序。


孟:Lisp 族类的语言在80 年代曾经兴起过,后来又平淡下去,请问这是为什么? 您认为历史会再给 Lisp 一个机会吗?为什么呢? Emacs这种体系结构以及Lisp/Scheme语言是否符合网络互联时代的计算模型?会不会被彻底淘汰?


洪:应该说Lisp是一批天才的创造。其中既有大量数学家的贡献,又有一批计算机科学家的贡献。它的产生是大大超前于它出生的那个时代的。Lisp 和Fortran 是最先发明的两种高级语言,一个用于数字计算,一个用于符号计算,但是他们的生命力现在仍然非常强。

以前的Lisp退出主流市场的原因不是Lisp的设计思想本身有问题,而是那个时代的计算机硬件资源与它不协调。你应该明白,50 年前1MB 内存的造价可能是一个天文数字。 八十年代时局面仍然没有根本的改观,但是进入二十一世纪后,计算机上的CPU与RAM资源都有了质的飞跃,而且现在还可以尝试使用“非冯诺伊曼”体系的并行计算机来运行Lisp 程序,Lisp 终于迎来了它的生命第二春。

Scheme是一个非常适合编写网络程序的语言。例如,你可以使用它编写CGI程序。原因我刚才已经说过了,Scheme 的程序与数据是采用相同格式表达的,现在互联网上流行的XML 就是Lisp 程序的一种更加丑陋的变形。


孟:如果我现在想开发一个软件,打算学习和模仿GNU Emacs 的体系架构,我是不是上来就得写一个Lisp解释器?这是不是有点不现实?好像快渴死的人跑去种西瓜一样。模仿GNU Emacs 体系架构有没有什么变通的方法?


洪:不应该采用这种本末倒置的方法来学习编程。我认为正确的方法应该是先理解Lisp背后的思想,特别是数学方面的关于计算本质的思想。我刚才说过,Lisp 背后的思想是许多数学家和计算机科学家长期思考的结果,这些东西是不会过时的。你难道听说过有什么数学定理过时吗?!只有理解了这些问题的实质,那么你眼中的Lisp会是另一个完全不同的模样。


孟:最后一个问题,听说您正在积极准备一个“黑客道”培训。能给我们简单介绍一下吗?


洪:“黑客道”培训计划是“一、百、万”工程的一个重要组成部分,目的在于为自由软件社团培训大量的人才。

你可能注意到这样的一个事实:许多计算机专业的大学生学习四年毕业后不会编写程序,缺乏动手实践的能力,这不能全怪这些学生学习不努力,而是我们的人才培养模式上存在重大的缺陷。我个人认为,目前国内软件界最缺乏的就是高级程序员,而目前高校的这种落后的教育体制下是难以培养出黑客的。

“黑客道”的培训计划是综合运用泛系理论和自由软件的产物,是培养计算机黑客的“生产线”。通过系统的培训,智商中等的学员可以在相对较短的时间内成为高级程序员,高级程序员可以像小汽车一样从流水线上批量地生成出来。以往绝大多数人认为编程是需要创新的工作,程序员是难以按照程序化的工序一步一步训练出来的。黑客道培训计划通过实践证明这是一种错误的观点,尽管编写软件是需要高度创造性的,但是培养具有创新精神的程序员的活动是有内在规律的,是有章可循的。“黑客道”培训计划以往的探索在这一点上取得了初步的成功。


孟:非常感谢您。这次专访的内容我会生成PDF文档交给您审校。

洪:你用什么生成PDF 文档?

孟:用Word 和Acrobat。

洪:看来你也是专有软件的受害者啊。Word 制作出来的PDF 很邋遢,就好像FrontPage 制作出来的HTML 页面一样。有机会希望你能加入CTUG(Chinese TexUser Group)。 CTUG 现在已经全面推广ConTeXt,这是目前最出色的扩展,而且对中文的支持一流,远远好于LaTeX。ConTeXt可以直接采用pdftex 来输出PDF 文件,而越南人搞的pdftex 的确很优秀。


0 0