acm-header
登录

ACM通信

实践

专用语言


说明组件说不同的语言

由约翰·赫西

在我大学的计算机科学实验室里,在漫长的编码和调试之夜的休息时间里,两场永恒的辩论不断地展开:“emacs vs vi?”和“什么是最好的编程语言?”后来,当我开始在工业界的职业生涯时,我注意到关于编程语言的争论也在硅谷校园的走廊里进行。那是20世纪90年代,在Sun微系统公司,我们中的许多人都看到Java在开发人员中占有重要的思想份额,特别是那些以前用C或c++开发的人。

我一直认为最好的语言不能太主观,太依赖于手头编程任务的性质。然而,在我的职业生涯中,我花了大量的时间思考两个我认为更基本的相关问题。首先,软件工程大体上是在进行的吗更少的语言随着时间的?也就是计算机语言的集合收敛?第二,是什么让一种特定的语言“更好”、有用或更迅速地用于某一特定任务?

在研究这些问题时,我发现特别有趣的是,不要着眼于重量级的较量,而是着眼于它们不那么深入研究的分支——the专用语言。这些语言就像野草一样在主流语言发展的道路上疯长,它们所展现的特性和历史让人重新思考基本语言问题的本能答案。考虑到专门构建的语言,编程语言的开发根本没有趋同,而且从语言设计的角度来看,实用程序似乎与传统的结构或属性概念没有什么关系,这些概念在经验上“更好”。特定目的构建的语言甚至不符合规范编译器语法的严格定义:它们在某种程度上看起来比成熟的编程语言“小”;它们并不总是图灵完备的;它们可能缺乏正式的语法(和解析器);它们有时是独立的,但往往是更复杂的环境或包含程序的一部分;它们经常被解释,但并不总是被解释;它们通常为单一目的而设计,但经常(意外地)从一种用途跳到另一种用途。有些甚至连名字都没有。

最重要的是,特定目的构建的语言常常是大型软件系统(如操作系统)开发的重要组成部分,无论是作为开发工具的一部分,还是作为大型环境中不同部分之间的粘合剂。因此,发掘这些鲜为人知的创作,并研究它们与我们更广泛的语言见解之间的联系,是一件特别有趣的事情。在我的职业生涯中,当我在几个商业操作系统和大型软件组件上工作时,我得出的结论是,新的语言不仅一直在开发,而且它们常常是大型软件系统发展和维护的组成部分。

Unix环境以易于连接的小工具为理念,是专门构建语言发展的理想温室。粗略浏览一下20世纪80年代早期的Unix手册,可以看到20多种形式的小语言正在积极地使用,如图1

这些语言从完整的编程语言(sh)到预处理器(yacc)到命令行语法(adb),再到状态机或数据结构的表示(正则表达式、调试器“stab”)。20年后,当Sun Microsystems发布现代Unix系统Solaris 10时,几乎所有新的重要操作系统特性都涉及到新的专门构建的语言的引入:DTrace调试软件引入了用于跟踪查询的D语言;故障管理系统包括一种用于描述故障传播的语言;区域和服务管理特性包括XML配置语法和新的命令行解释器。

这些小型Unix语言之一的历史,即adb调试器的历史,特别说明了一些小型但在大型系统中有用的东西的偶然演变和粘性。

回到顶部

进化胜过智能设计

Unix的早期开发发生在DEC PDP系统上,它有一个非常简单的调试器,称为ODT,或八进制调试技术。(这个了不起的名字让人想起一种秘密的功夫,它可以使PDP的12位寄存器瘫痪。)ODT程序支持一种非常原始的语法:在每个命令的开始处指定一个八进制物理内存地址,并以单个字符(例如,B表示断点)或斜杠(/)作为后缀,以读取和写入该内存位置的内容,如图2

于是,一种小语言诞生了。ODT语法显然启发了在PDP上开发的新Unix系统的第一个调试器的形式,它被简单地称为db。在1971年的Unix v3时代,db命令语法借用了基本的ODT模型,并开始使用额外的字符后缀来定义寻址模式和格式化选项,如图3

到1980年,db被adb取代,adb被包含在AT&T SVR3 Unix发行版中。经过几年的发展,该语法已经添加了新的调试命令,现在不仅支持简单的地址,还支持算术表达式(123 + 456/现在是合法的)。另外,"/"后面的字符现在表示数据格式,而"”或“表示行动。adb语法如图4

增加了"$ <读取命令的外部文件是特别有趣的,因为它催生了原始的adb程序或宏的开发,这些程序执行一系列命令,以在特定的内存地址显示C数据结构的内容。也就是说,要显示一个内核进程结构,你需要取它的地址,然后输入"$ < proc来执行一系列预定义的命令来显示一个进程的C数据结构的每个内存。1984年的SunOS 4中proc宏的内容显示在图5.为了让这个输出更容易理解,"/"命令现在可以用引号括起来的字符串标签、换行符(n)和标签(16 t)被包括在解码数据中。变量"."计算的结果是应用宏时使用的输入地址,而"+"变量计算的结果是输入地址按前面所有格式字符的字节数加1。然后使用内核源代码维护宏。

十多年后的1997年,我在Sun公司工作,开发后来的Solaris 7。这个版本是我们的第一个64位内核,但是我们选择的内核调试工具仍然是adb,就像在1984年一样,我们的源代码库现在包含了数百个有用的宏文件。不幸的是,adb的实现基本上不可能从32位移植到64位来调试新内核,因此,开发具有更多现代调试器特性的干净的新代码库的时机似乎已经成熟。

当我考虑如何最好地解决这个问题时,我被这样一个事实震惊了:尽管adb的代码库脆弱、非结构化,但它的关键特性是,它的语法深深植根于我们所有最有经验和最有效的工程师的思想和行为中。(正如当时有人恰当地说:“它在手指中。”)所以我开始构建一个新的模块化调试器(mdb),它将支持用于高级内核调试和其他现代功能的API,同时将保持与现有语法和宏的精确向后兼容。在一个新的前缀("::")之后添加复杂的新特性,这样它们就不会破坏现有的语法(例如,":: findleaks来检查内核内存泄漏)。整个语法最终被正确编码为yacc解析器。宏文件被淘汰,取而代之的是编译器生成的调试信息,但是“$<”语法被保留为别名。又过了十年,mdb仍然是OpenSolaris内核事后调试的标准工具,并被数百名程序员扩展。

调试器的故事说明了一个小的专门构建的语言可以在本质上随机进化,没有清晰的设计,没有一致的语法或解析器,没有名称,但却在操作系统中持续发展了40多年。在同一时期,许多主流语言出现并走向更广阔的领域(Algol、Ada、Pascal、Cobol等等)。从根本上说,这个调试器能够存活下来的原因只有一个:它简明扼要地编码了用户执行的确切任务,从而连接到这些用户。取一个地址,导出它的内容,找到下一个地址,跟随它到下一个感兴趣的位置,导出它的内容,等等。对于专门构建的语言来说,与任务和该任务的用户社区的深入联系通常比巧妙的设计或优雅的语法更有价值。

回到顶部

突变和杂交

突变,一些是偶然的,一些是有意的,通常在有目的构建的系统语言的开发中扮演着关键的角色。一种常见的变体形式是将一种语言的语法子集(例如,表达式或正则表达式)添加到另一种语言中。这种类型的变异可以使用预处理器来实现,预处理器可以将一种高级形式转换为另一种形式,或者将预处理语法与目标语言的目标语法混合。突变可能会分化到足以形成一种新的混合语言。解析器工具yacc和bison是最著名的完全混合语言示例:语法被声明为一组与C代码混合的解析规则,C代码根据规则执行;然后,实用程序发出一个完成的C程序,其中包括规则代码和用于在语法上执行解析状态机的代码。

在早期的Unix中,这种类型的变异的另一个例子是Kernighan开发的Ratfor (Rational Fortran)预处理器。Ratfor允许作者用C表达式和逻辑块编写Fortran代码,结果被翻译成带有行号和goto语句的Fortran语法,如图6

一种更奇怪的突变语言是C语言和Algol语法的混合,使用C预处理器开发,并在adb的代码中使用。显然,类似于Algol的Unix sh语法的作者史蒂夫·伯恩(Steve Bourne)确定,Algol的一些基因组会在物种中继续存在。中显示了一些示例代码图7

唉,后来的代码版本通过预处理器运行,然后签入,以便于维护。许多未来的语言已经包含了更明确的杂交设计,以方便从一个环境过渡到另一个环境。随着C语言的广泛采用,它的表达式语法进入了数量惊人的大大小小的新语言,包括Awk、c++、Java、JavaScript、D、Ruby和许多其他语言。类似地,在Perl取得成功之后,许多其他脚本语言采用了它对正则表达式语法的有用扩展作为一种新的规范形式。像表达式语法这样的核心概念通常构成了小型语言的主体部分,并且借鉴已建立的模型可以快速实现语言并被用户快速采用。


取一个地址,导出它的内容,找到下一个地址,跟随它到下一个感兴趣的位置,导出它的内容,等等。对于专门构建的语言来说,与任务的深度联系往往比巧妙的设计或优雅的语法更有价值。


回到顶部

共生

在大型软件系统的开发中,小语言通常与主流开发语言或软件系统本身保持共生关系。前面描述的adb宏语言可能无法在其Unix父级的源代码基础之外存活下来。您最喜欢的电子表格的宏语言是另一个例子:它的存在提供了一种方便的方法来操作包含的软件应用程序的用户可见的抽象。

在操作系统领域,我最喜欢的鲜为人知的共生例子是Sun公司创建的Forth和SPARC汇编语言的联合,作为Open-Boot固件工作的一部分。我们的想法是在SPARC工作站上创建一个小型解释器作为引导环境。Forth被选为新硬件的引导和硬件启动环境,因为语言内核很小,可以在新的处理器和平台上立即启动。然后,使用Forth字典,可以在解释器中动态定义新的命令,用于调试。由于Forth允许它的字典在解释器中重写单词(标记)的定义,有人提出了一个创造性的想法,将解释器用作硬件的宏汇编器。创建了一组字典,用Forth代码重新定义了SPARC中的每个操作码(“ld”、“move”、“add”等等),这些代码将计算汇编指令的二进制表示,并将它们存储到内存中。因此,整个低级函数都可以用汇编语言编写,并以Forth头作为前缀,然后输入到微型解释器中,然后在解析标记并执行结果例程时在内存中组装目标代码。

近年来,Web浏览器已经成为变异和共生的沃土。现代Web开发中的两个核心人物是解释JavaScript和XML。(XML本身是各种其他语言的语法,是混合语言和变体语言的丰富来源。)在常见的Ajax编程模型中,可以将JavaScript对象序列化为XML形式,并且可以使用XML编码将远程过程调用传回服务器。在这种编码中,XML-RPC为浏览器客户机提供了一种称为multicall的标准扩展,用于在一次传输中从客户机向服务器发出多个过程调用。中显示了一个对方法x.fo的单个调用,然后使用multicall对同一方法进行一系列调用的示例图8

在为一系列新的存储产品实现Ajax用户界面代码时,Sun Fishworks团队希望开发一种方法来最小化不必要的客户机-服务器交互。第一个发展出来的概念是多调用其参数为另一个调用结果的调用。在下面的例子中,方法x.foo调用的结果是x.bar在单个XML-RPC交互中,如图9

这里的技巧是新的结构成员methodParams指示下一个成员不是静态参数,而是递归调用的更多方法,并将结果压入堆栈。一旦堆栈诞生了,就很自然地开始引入基于堆栈的语言中的操作符,形成一种全新的解释语言,它本身用JavaScript声明为数据,通过现有的XML-RPC序列化发送到服务器,并通过扩展到XML-RPC解释器引擎来执行。我们在Sun实现的一些操作符如下所示图10

这个例子说明了JavaScript和XML之间的共生关系,它本质上允许我们的语言在不需要自己的lexer或解析器的情况下存在,并从根本上服务于将对性能至关重要的代码从JavaScript转移到我们的服务器并最小化往返。在电子游戏行业中,Lua和C/ c++之间也存在类似的共生关系(没有混合语法)。Lua脚本语言为在电子游戏引擎中编写非性能关键代码提供了一种流行的形式,而且Lua解释器设计使其很容易与C代码建立桥梁。

一旦两种或两种以上的语言在一个大型软件系统中进行交互,围绕它们出现一个工具生态系统(很可能结合了带有混合语法的小语言)来简化整个系统的维护、开发和调试就变得很自然了。如果一个丰富的生态系统围绕着一个完整的软件系统的语言(无论大小、专门构建的还是通用的)生长,那么整个环境繁荣的时间越长,其组成部分生存的时间就越长。因此,当我们把软件抽象的塔建得越来越高时,我们应该期待看到和知道更多的语言,而不是更少。

回到顶部

作者

迈克·夏皮罗(mws@sun.com)是Sun Microsystems的杰出工程师,目前领导Sun公司位于旧金山的Fishworks高级工程团队。他之前从事过Sun内核工程工作,在那里他为Solaris开发了各种技术,包括pgrep、pkill、mdb、dumpadm、libproc、CTF、fmd、DTrace D语言和编译器、smbios,以及各种与CPU、内存、I/O和软件故障处理和诊断相关的特性。

回到顶部

脚注

本文的前一个版本出现在ACM队列, 2009年1月。

DOI: http://doi.acm.org/10.1145/1498765.1498781

回到顶部

数据

F1图1。Unix中的小语言,20世纪80年代初。

F2图2。ODT-8调试器语法,大约1967年。

F3图3。Unix V3“db”语法,大约1971年。

F4图4。AT&T SVR3“adb”语法,大约1980年。

F5图5。在SunOS 4上调试“proc”结构,大约在1984年。

F6图6。Fortran和Ratfor,大约在1975年。

F7图7。来自“adb”早期的C和Algol突变体。

F8图8。JavaScript, XML-RPC和Multicall。

F9图9。参数化结果的多播。

F10图10。使用堆栈运算符的多集合。

回到顶部


©2009 acm 0001-0782/09/0400 $5.00

允许制作本作品的全部或部分的数字或硬拷贝用于个人或课堂使用,但前提是该拷贝不是为了盈利或商业利益而制作或分发,并且该拷贝在第一页上带有本通知和完整引用。以其他方式复制、重新发布、在服务器上发布或重新分发到列表,需要事先获得特定的许可和/或付费。

数字图书馆是由计算机协会出版的。版权所有©2009 ACM有限公司


没有发现记录

登录为完全访问
»忘记密码? *创建ACM Web帐户
文章内容:
Baidu
map