On this page:
三版附言

本书引你直面计算机编程中最基本的思想:

计算机语言的解释器不过是另一个程序

听上去显而易见,不是吗?但是含义颇深。若你是计算理论学家,解释器思想会使你想起哥 德尔发现的形式逻辑系统局限,图灵的通用计算机概念,还有冯诺依曼存储程序机的基本思 想。若你是程序员,掌握解释器思想则是巨大力量的源泉。它如灌顶醍醐,能彻底改变你思 考编程的方式。

听说解释器之前,我便没少编程,也开发过一些大型程序。其中一例是由 PL/I 写成的大型 数据和信息检索系统。实现系统时,我把 PL/I 看作一批固定的规则,由一些遥不可及的语 言设计者订立。我认为我的工作不是修改这些规则,甚或深入理解它们,而是从(极为)浩 繁的手册中挑挑拣拣,选出这种那种功能来用。我从没想过有一些潜藏的结构组织语言,也 不觉得我可能得推翻语言设计者的一些决定。我不知道如何创造嵌合的子语言来帮我组织系 统实现,所以整个程序不是各部分能够灵活组合的几种语言,倒像幅庞杂的马赛克,每片都 得小心塑造和放置。不理解解释器,你还是能编程,甚至能成为一名称职的程序员,但你无 法成为大师。

作为一名程序员,出于三个原因,你应该了解解释器。

首先,在某些时刻,你得实现解释器——也许不是全能而通用语言的解释器,但终归是解释器。 几乎每一个与人灵活交互的复杂计算机系统——例如,计算机绘图工具或信息检索系统——都包 含某种解释器来组织交互。这些程序可能包含复杂的单项操作——在显示屏的一个区域加上阴 影,或执行数据库搜索——但解释器是胶水,使你将单项操作组合成有用的模式。你能把一项 操作的结果当作另一操作的输入吗?你能给一个操作序列命名吗?名字是局部的还是全局的? 你能给一个操作序列加上参数,并给它的输入命名吗?诸如此类。不论单项操作多么复杂精 巧,常常是胶水的质量最直截地决定系统的能力。很容易找到单项操作良好,但胶水糟糕的 程序;回头来看,我觉得我的 PL/I 数据库程序胶水实在糟糕。

其次,即使程序本身不是解释器,也包含类似解释器的重要片段。细究一个复杂的计算机辅 助设计系统,你可能发现协同工作的几何图形识别语言,图形解释器,基于规则的控制解释 器,以及面向对象语言解释器。组织复杂程序最有效的方式之一是视之为一组语言,每种语 言从不同角度,以不同方式处理程序元素。为合适的目的选择合适的语言,理解实现时的利 弊权衡:这就是解释器研究的内容。

学习解释器的第三个原因,是直接涉及语言结构的编程技术日益重要。如今,操作面向对象 系统中类的层次备受关注,而这只是该趋势的一个例子。这可能是无可避免的结果,因为我 们的程序正日趋复杂——多留心语言可能是应对这种复杂性的最好工具。再想想那个基本思想: 解释器本身不过是一个程序。但那程序以某种语言写就,其解释器本身不过是另一程序,以 某种语言写就,其解释器本身又是……或许截然区分程序和编程语言是个误导性的想法,未 来的程序员不再自视为编写某种程序,而是为每个应用创造新的语言。

弗里德曼和宛德完成了标志性的工作,他们的著作势将改变编程语言课程的图景。他们不只 给你讲讲解释器,他们展示给你看。本书的核心是一系列精妙的解释器,始 于一种高级抽象语言,逐步揭示语言特性,直至一状态机。事实上你可以运行这些代码,研 习和修改它们,改变这些解释器的处理方式,诸如定界、参数传递、控制结构,等等。

作者以解释器研究语言的执行,并展示了如何用同样的思想不加运行地分析程序。在新增的 两章中,他们展示了如何实现类型检查器和推导器,以及这些特性在现代面向对象编程语言 中如何交互。

这种方法之所以吸引人,部分在于作者选择了优良的工具——Scheme 语言。这种语言结合了 Lisp 的统一语法和数据抽象能力,以及 Algol 的词法定界和块状结构。但利器到了大师手 中方能物尽其用。本书的示例解释器足可垂范。确实如此,因为他们是可以运行的 示范。我确信这些解释器和分析器将在未来数年现身于许多编程系统的核心之中。

这不是本容易的书。解释器难以掌握不是没有原因的。语言设计者要比普通应用的开发者更 加远离最终用户。设计应用程序时,你考虑将要完成的具体任务,以及将要包含的功能。但 设计语言时,你考虑人们想要实现的种种应用,以及实现它们的可能方式。你的语言应该是 静态作用域还是动态作用域,或者二者兼有?它应该有继承吗?它传递参数时是传引用还是 传值?续文应该是显式的还是隐式的?这些都取决于你想让你的语言如何使用,哪 些程序应该很容易编写,哪些程序可以不那么易写。

此外,解释器实在微妙的程序。解释器中一行代码的简单改变都能使最终语言的 行为发生剧变。别想粗读这些程序——即使是相对简单的代码,世上也绝少有人能够一瞥新的 解释器就预测它的行为。所以钻研这些程序吧。最好,运行它们——这些是能运行的 代码。试着解释一些简单的表达式,然后试试更复杂的。添加错误信息。修改解释器。设计 你自己的变体。试着真正掌握这些程序,而不是对之如何工作粗具印象。

如果你这样做了,你对编程和身为程序员的自己看法定会不同。你将自视为语言的设计师, 而不仅是语言的用户。你将成为语言组成规则的主人,而不仅是他人所选规则的随从。

三版附言

上述序言写于短短七年之前。在九十年代,难以想象七年之内,信息应用和服务已遍布全球。 它们由持续增长的编程语言和编程框架驱动——这一切都基于不断壮大的解释器平台。

想创建 Web 页面吗?在九十年代,那等于格式化静态文本和图像,实际上是创建由浏览器 运行的程序,而浏览器仅仅执行一条“print”语句。今天的 动态网页充分利用脚本语言(解释性语言的另一名字),如 Javascript。浏览器程序则相 当复杂,包含对 Web 服务器的异步调用。而服务器通常运行着另一程序,由完全不同的编 程框架写就。框架又可能含有多种服务,每种服务都有各自的语言。

或者你要创建一个机器人,在大型多人在线游戏——如魔兽世界——中改善形象。这时,你可能 使用 Lua 之类的脚本语言——还可能支持面向对象扩展——来协助表达各类行为。

又或者你在为大型计算机集群编程,进行全球规模的索引和搜索。这时,你可能用函数式编 程中的 map-reduce 范式来编写程序,以减轻明确安排处理器调度细节的负担。

又或者你在为传感器网络开发新的算法,并且尝试用懒求值更好地解决并行和数据汇集问题。 又或者你在探索 XSLT 那样的转换系统来控制 Web 页面。又或者你在设计转换和重混多媒 体流的框架。又或者……

多少新应用!多少新语言!多少崭新的解释器!

依旧,编程新手,甚至有经验的人,可以分别看待每种框架,在既定的规则内工作。但创造 新框架要求大师级技能:理解语言运行背后的原理,挑选最适合每种应用的语言特性,熟知 如何创造带给语言生命的解释器。这些是你能从本书学到的技能。

Hal Abelson 剑桥,马萨诸塞州 2007年9月