在 OOPSLA '98(面向对象编程、系统、语言和应用)的主题演讲中,Sun Microsystems 研究员 Guy L. Steele Jr. 说:“从今以后,设计语言的主要目标应该是为增长做好计划。” 函数、用户定义的类型、运算符重载和泛型(例如 C++ 模板)已不再足够:未来的语言必须允许程序员向程序添加全新的信息种类,并控制信息的处理方式。
本文认为,下一代编程系统可以通过结合以下三项特定技术来实现这一目标:
• 编译器、链接器、调试器和其他工具,它们是插件框架,而不是单体应用程序。
• 允许程序员扩展其语法的编程语言。
• 以 XML 文档形式存储的程序,以便程序员可以统一表示和处理数据和元数据。
这些创新可能会像 1970 年代的结构化语言、1980 年代的对象以及 1990 年代的组件和反射一样,深刻地改变编程。为了了解原因,我们必须首先检查程序员今天使用的系统的缺点。让我们从两个最流行的系统开始:Unix 命令行和微软的 COM(组件对象模型)。
如果成功以寿命来衡量,那么 Unix 命令行是历史上最成功的编程系统。它已有 30 多年的历史,仍然是许多开发人员最喜欢的环境。
传统观点将其威力归因于其 “小工具大组合” 哲学:程序员不必从头开始编写所有内容,只需使用几个按键即可通过组合 wc、grep 及其同类工具来创建复杂的数据处理管道。之所以能够实现这一点,是因为这些工具使用通用的数据格式和通信协议。格式是换行符分隔的字符串列表;协议是标准输入、标准输出和 exit(rc)。这些约定共同定义了一个简单的组件模型;任何遵守这些约定的程序都可以与其他任何程序一起工作,无论每个程序是用哪种语言编写的。
Unix 模型非常适用于系统管理和简单的文本处理。但是,它无法可靠地处理无法表示为记录流的数据。特别是,程序本质上是树状结构的,无法使用正则表达式(命令行武器库中最强大的武器)准确解析。因此,标准的 Unix 工具无法完成像更改变量名称这样简单的事情。
命令行工具的第二个缺点是,命令行标志是指定控制流程的笨拙方式。 每个人都同意单行程序是不好的(好吧,除了铁杆 Perl 粉丝),但这实际上是 find 等工具要求用户编写的。 更糟糕的是,每个工具的命令行微型语言都与其他工具的不同。 尝试坚持使用简单的开/关选项会导致像 gcc 这样的怪物,它现在有太多的标志,以至于程序员正在使用遗传算法来探索它们。1
许多程序员没有放弃面向行的工具,而是转过头去不理会他们无法处理的问题。其他人则转向了组件系统,例如微软的 COM。 COM 没有要求程序员将所有内容表示为字符串列表,而是让他们在内存中传递数据结构。 程序员不必通过命令行微型语言的狭窄过滤器来挤压他们的意图,而是可以使用循环、条件、方法调用以及熟悉语言的所有其他功能来指定他们的愿望。 结果是,今天的 Windows 开发人员可以使用 Visual Basic、C++ 或 Python 编写程序,使用 Visual Studio 编译和运行程序,使用 Excel 分析其性能,并使用 Word 检查最终报告的拼写。
至关重要的是,开发人员还可以通过编写插件来扩展这些产品。 例如,Visual Studio 通过定义良好的 API 与版本控制系统交互。 只要有人愿意编写桥接代码,任何在 Windows 上运行的版本控制系统都可以直接从 Visual Studio 的按钮和菜单驱动。 类似的 API 允许流行的内存检查工具 Purify 代替 Visual Studio 自己的调试器,等等。
可插拔性是当今大多数复杂应用程序的核心。 以 Apache 为例:每个人都知道它是一个 Web 服务器,用于响应 HTTP 请求发送 HTML 页面。 鲜为人知的是,它的插件系统使其可以成为其他应用程序的平台。 通过编写模块来检查和修改通过服务器的请求,开发人员可以使 Apache 提供身份验证服务、版本控制、在线游戏等等。
21 世纪早期的伟大讽刺之一是,为他人构建组件系统的程序员奇怪地不愿意将其自己的工具组件化。 编译器和链接器仍然是单体命令行应用程序:文件输入,文件输出,控制中间发生的事情的唯一方法是通过命令行标志或嵌入式的、供应商特定的指令。 程序员无法选择性地调用解析器、分析器或代码生成器,也无法插入自定义模块来更改程序的处理方式。 (这不是开源与闭源问题:GCC,GNU 编译器集合,并不比商业编译器更像插件框架。)
但是为什么有人想要做这些事情呢? SUIF(斯坦福大学中间格式)给出了一个答案,它是一个允许用户插入自己的优化模块的编译器。2 开发人员可以通过编写过滤器并将其添加到编译器的配置中,来向 SUIF 添加新的或改进的优化。 这样做并不简单,但与 Apache 插件一样,一旦一位开发人员完成了它,其他人就可以立即分享好处。
现在,考虑一下您最喜欢的调试器——或者更确切地说,将其与您最喜欢的编辑器进行比较。 您可以为后者编写宏;为什么不为前者编写宏呢? 为什么程序员不能在库中包含代码来控制调试器如何显示这些库创建的数据结构? 几乎所有的调试器都将结构显示为树。 这对于浅层非循环结构来说是足够的,但对于大型循环结构来说,这是令人沮丧或具有误导性的。 如果调试器是可编程组件,或者程序员可以在库中插入显示回调,他们就可以让用户更深入地了解他们的程序在做什么。 例如,诸如 DDD(数据显示调试器,https://gnu.ac.cn/software/ddd/)之类的图形调试器未能流行的原因之一是,它们根据分配的内存块显示所有数据结构。 如果图形调试器将树显示为树,将队列显示为队列,程序员将更可能使用它们。
使编程工具成为可插拔框架将在各个方面都很有用,而且我认为这对于支持可扩展语言至关重要。 为了了解原因,我们必须了解可扩展性的含义,以及它在哪些方面取得了成功。
编程语言通常通过形式化和概括其时代最佳实践来发展。 良好嵌套的 goto 语句变成了结构化编程的条件和循环;仅通过伴随函数访问的记录变成了对象;除了数据类型之外完全相同的函数变成了泛型,等等。
可扩展语言是一种将这种能力掌握在每个人手中的语言,而不是将其保留给标准委员会。 语法可扩展语言允许程序员通过指定新语法的样子以及它如何映射回语言的原语来定义新形式。 语义可扩展语言允许程序员定义全新的操作种类,或更改内置操作的行为。 C 宏和 C++ 运算符重载可能是每种可扩展性最熟悉的例子,尽管两者都受到严格限制。
Lisp 及其子代 Scheme 展示了全心全意的可扩展性有多么强大。 Scheme 程序员经常使用其卫生宏来为其特定问题领域进行自定义。 例如,宏定义
(define-macro when
(lambda (test . branch)
`(if ,test
(begin ,@branch))))
告诉 Scheme 将
(when (>= pressure limit)
(open-valve 20)
(close-valve))
翻译成
(if (>= pressure limit)
(begin
(open-valve 20)
(close-valve)))
一个不太平凡的例子来自 Java 语法扩展器,它是 Java 的卫生宏系统。3 使用它,程序员可以定义转换,将此
check a.equals(b) throws NullPointerException;
变成此
try {
logCheck(“a.equals(b) throws NullPointerException”);
a.equals(b);
noThrowFailure();
} catch (NullPointerException e) {
checkPassed();
} catch (Throwable t) {
incorrectThrowFailure(t);
}
我们无需证明就可以断言,如果程序员可以使用更短、更易读的形式编写单元测试,他们会创建更多的单元测试。
语言可扩展性已经存在多年,但仍然是学术界的好奇心。 阻碍其普遍采用的三个问题是:其不熟悉性、主流语言中缺乏对其的支持以及程序员编写的内容与他们必须调试的内容之间的认知差距。 第一个问题正在被诸如 XDoclet4之类的代码生成器以及用于在 Enterprise JavaBeans5 和 .NET6 中生成清单和其他样板代码的向导慢慢侵蚀。 第二个问题是第一个问题的结果:随着程序员越来越适应程序转换工具,语言支持应该不可避免地随之而来。 例如,Java 1.5 允许程序员注释他们的程序,以便
public class CoffeeOrder {
@Remote public Coffee [] getPriceList() {
…
}
@Remote public String orderCoffee(String name,
int quantity) {
…
}
}
可以转换为
public interface CoffeeOrderIF extends java.rmi.Remote {
public Coffee [] getPriceList()
throws java.rmi.RemoteException;
public String orderCoffee(String name, int quantity)
throws java.rmi.RemoteException;
}
public class CoffeeOrderImpl implements CoffeeOrderIF {
public Coffee [] getPriceList() {
…
}
public String orderCoffee(String name, int quantity) {
…
}
}
这留下了第三个也是最棘手的问题:程序员编写的内容与他们必须调试的内容之间的认知差距。 这种差距是许多程序员不喜欢向导和其他代码生成器的原因。 如果生成的代码行为不端,程序员必须
• 放弃它并手工编写代码(就像高级生成器不存在一样)。
• 编辑和调试向导生成的代码(这总是比程序员编写的代码更复杂)。
• 盲目地调整源代码,希望产生程序员最初想要的输出。
第一个选项使许多程序员相信代码生成器是在浪费时间;第二个选项导致无法维护的意大利面条式代码;第三个选项导致绝望。
弥合这一差距的唯一可扩展方法是让程序员控制链接器、调试器和其他工具如何处理生成的代码。 当程序员向语言添加新内容时,他们应该能够将模块插入编译器,以告诉编译器如何为新功能生成代码,并将另一个模块插入调试器,以告诉调试器如何显示该功能的用法。
明天的程序员将使用动态库,而不是今天的“被动”库,后者仅包含最终程序的代码和数据。7 这些动态库不仅包含要包含在最终应用程序中的内容,还包含指示处理工具如何分析、优化和调试该内容的说明。 正如已经提到的,只有少数程序员需要创建这样的库来使其他所有人更有效率,就像今天只有少数人需要为 Apache 和 Visual Studio 创建插件一样。
可插拔框架和动态库将有助于使可扩展性变得易于访问,但仅靠它们是不够的。 为了了解原因,我们必须仔细研究为什么可扩展性在 Lisp 及其后代中如此成功,但在其他地方却未能流行起来。
几十年来,程序员一直在开玩笑说 Lisp 代表 “大量的烦人的单括号”。 在这些玩笑背后隐藏着一个深刻的想法:在 Lisp 中,程序和数据都表示为嵌套的 s-表达式。 这鼓励 Lisp 程序员将程序视为数据,并以与处理其他所有事物相同的方式处理程序。
大多数程序员对 Lisp 的前缀表示法和括号嗤之以鼻。 然而,这些程序员已经争先恐后地采用了 XML。 XML 最初旨在表示数据,但也被用于表示程序。 其中最著名的例子可能是 JSP(Java 服务器页面),它允许程序员将 Java 程序片段嵌入到 HTML 中。 当用户从 Web 服务器请求 JSP 时,服务器将 JSP 编译为纯 Java Servlet,然后编译并运行 Servlet,并将其输出发送给用户。 例如
<HTML>
<BODY>
<TABLE BORDER=2>
<%
for (int i = 1; i<=10; i++) {
%>
<TR>
<TD><%= i %></TD>
<TD><%= i*i %></TD>
</TR>
<%
}
%>
</TABLE>
</BODY>
</HTML>
变成
class MyPage extends Servlet {
public String handleRequest(
ServletContainer sc,
Request req,
Response res
) {
PrintWriter out = res.getWriter();
out.println(“<HTML>”);
out.println(“<BODY>”);
out.println(“<TABLE BORDER=2>”);
for (int i=1; i<=10; i++) {
out.println(“<TR>”);
out.println(“<TD>” + (i) + “</TD>”);
out.println(“<TD>” + (i*i) + “</TD>”);
out.println(“</TR>”);
}
out.println(“</TABLE>”);
out.println(“</BODY>”);
out.println(“</HTML>”);
}
}
这种方法有很多值得推荐的地方,但它也有一些严重的缺点。 从积极的一面来看,JSP 允许程序员和图形设计师就地查看他们的代码。 JSP 还支持可扩展性:JSP 库为常见操作预定义了许多标签,但用户可以通过定义自定义标签并告诉 JSP 系统在使用这些标签时调用哪些类的方法来扩展这些库。8
从负面方面来看,JSP 是前面讨论的认知差距的主要例子。 如果复杂的 JSP 中出现问题,其作者必须浏览数页机器生成的 Java 代码,或逆向工程翻译过程才能提出修复方案。
来自 Apache 软件基金会的 Ant 是另一个混合系统,它也具有这些优点和缺点。 Ant 被开发为平台无关的 Make 替代品,已成为 Java 的事实上的标准构建工具。 Ant 使用 XML,而不是自定义语法,因此可以使用现成的 XML 工具来创建、检查和修改 Ant 构建文件(图 1)。 但是,Ant 与许多混合系统共享一个弱点:Ant 文件中重要的许多内容在 XML 级别上是不可见的。 诸如 ${src} 和 ${dist}/lib/MyProject-${DSTAMP}.jar 之类的变量引用比它们深层嵌套的 XML 等价物更易于人类阅读和编写,但 XML 处理工具却看不到。 为了分析和操作 Ant 文件,程序必须执行第二轮非标准的解析。
XSL 用于将 XML 转换为 HTML 和其他文本格式,也存在此缺陷。 XSL 是一种或多或少声明性的语言,基于具有 forall 和条件构造的匹配/替换执行模型。 它比其他替代方案(例如在 Java 或 Perl 中转换 XML)更简洁,甚至有源代码级调试器,但再次强调,XSL 程序中重要的许多内容对 XML 解析器是不可见的。 例如,考虑这个简单的 XSL 程序
<BODY bgcolor=”{/Member/FavoriteColor}”>
Welcome <xsl:value-of select=”/Member/Name”/>!
<xsl:if test=”/Member/@level=’gold’”>
Our special offer to gold members today is now open.
</xsl:if>
Your phone numbers are
<TABLE border=”1” width=”25%”>
<TR><TH>Type</TH><TH>Number</TH></TR>
<xsl:for-each select=”/Member/Phone”>
<TR>
<TD><xsl:value-of select=”@type”/></TD>
<TD><xsl:value-of select=”.”/></TD>
</TR>
</xsl:for-each>
</TABLE>
</BODY>
第一个 if 测试检查个人的会员级别。 测试中的条件对于 XML 解析器是不可见的:它们看到的只是一个字符串,必须将其交给专用工具进行分析。
如果混合表示如此笨拙,为什么语言设计者还要继续使用它们呢? 答案很简单:“纯” XML 读写起来令人不愉快。 XML 中的嵌套越明确,人们就越难理解它。
但是他们为什么要这样做呢? 为什么在 21 世纪初期,程序员仍然坚持他们的工具必须为源文件中的每个字节在屏幕上绘制完全一个字形? 没有人期望 AutoCAD 或 Microsoft Word 这样做;即使是饱经风霜的 Unix 狂热分子也不期望能够使用 Vi 或 Emacs 打开关系数据库。 21 世纪早期的伟大讽刺之一是,秘书可以轻松地将组织结构图或隔间平面图放入电子邮件中,但使这一切成为可能的程序员却无法将类图放入他们的代码中。
我们认为,下一代编程系统最有可能将源代码存储为 XML,而不是平面文本。 程序员将看不到或编辑 XML 标签;相反,他们的编辑器将渲染这些模型以创建人性化的视图,就像 Web 浏览器和其他 WYSIWYG 编辑器一样。 例如,像这样存储在磁盘上的程序
<doc>仅替换低于阈值的内容</doc>
<cond>
<test>
<compare-expr operator=”less”>
<field-expr field=”age”>
<evaluate>record</evaluate>
</field-expr>
<evaluate>threshold</evaluate>
</compare-expr>
</test>
<body>
<invoke-expr method=”release”>
<evaluate>record</evaluate>
</invoke-expr>
</body>
</cond>
将被查看和编辑为这样
// 仅替换低于阈值的内容
if (record.age < threshold) {
record.release();
}
至关重要的是,代码不会作为 XML 文档中未解释的 CDATA 存储,程序员也不会看到(更不用说键入)XML 标签。 这样的表示形式将具有 JSP、Ant 和其他混合系统的所有缺点,而不会带来任何实际的好处。 相反,XML 将表示程序的深层结构。 只有时间和实验才能说明这最终会变成类似带注释的语法树还是更抽象的东西。
使用 XML 将软件模型与软件视图分离将带来几个好处。 首先,它将使语言更具可扩展性。 以 XML 形式存储的程序将比以任意 ASCII 标记集合形式存储的程序更容易处理。 特别是,程序员将能够将 XSL 和其他工具应用于它们——这些工具对于明天的程序员来说将像今天的正则表达式一样熟悉。9
其次,它将通过让程序员标记他们的代码来指示哪些部分用于哪些工具,从而简化动态库的构建,所有这些都在一个文件中完成,并且都使用一种表示法。 程序员还可以在他们的代码中嵌入任意内容,包括数学(使用 MathML)、类图(使用可缩放矢量图形,或 SVG)以及 CASE(计算机辅助软件工程)工具所需的所有元数据。 因此,Donald Knuth 的 “文学编程” 之梦可以实现。10
最后,程序员可以停止争论花括号应该放在哪里,因为他们将能够自定义他们对软件的视图,而无需修改底层模型。 例如,程序员可以轻松地选择将之前的代码片段视为这样
28. if (record.age < threshold) /* 仅替换低于阈值的内容 */ 29. { 30. record.release(); 31. }
甚至这样
;;; 仅替换低于阈值的内容 (if (< (record ‘age) threshold) (record ‘release))
而无需更改底层表示形式。 (这在今天的 Microsoft .NET 中几乎是可能的。 正如许多人所观察到的,它是世界上第一个 “可换肤” 的编程系统:C#、VB.NET 和其他语言具有相同的语义、内置类型和库,并且仅在语法的 “肤浅” 细节上有所不同。)
是的,所有这些在 20 年前使用 s-表达式就可以完成。 然而,尽管带括号的列表很有吸引力,但它们未能赢得程序员的心。 相比之下,XML 在不到十年的时间里就成为历史上最流行的数据格式。 今天,每个大型应用程序都可以处理它;每种编程语言都包含用于操作它的库;每个年轻的程序员都像上一代人熟悉字符串流一样熟悉它。 s-表达式可能应该获胜,但 XML 获胜了。
实现目标
在未来十年中,要解决许多技术挑战,才能使可扩展编程系统取得成功。 然而,最大的挑战将是社会性的。 告诉程序员您将完全重新架构他们的工具,他们会点头表示同意。 告诉他们您将以平面 ASCII 以外的其他形式存储程序,他们就会开始抱怨。
许多人发誓他们永远不会使用不 “按原样” 存储程序的系统,他们方便地忽略了一个事实,即需要数十万行设备驱动程序、操作系统和图形界面才能将磁盘上的磁旋转转换为屏幕上的字符。 仅删除一层解释,程序看起来就像图 2 中所示的那样。 再删除一层,我们将得到一个数字字符代码表;再删除一层,我们将得到一个比特流。 因此,反对添加另一层是虚伪的(除非提出异议的人仍然使用纯文本 Web 浏览器,例如 Lynx)。
这也忽略了一个事实,即越来越少的文档以每字符字节的 ASCII 格式存储;越来越多的文档使用某种字符编码方案来表示 Unicode,然后编辑器动态地解释 Unicode。 随着 Unicode 和 XML 文档变得无处不在,动态应用样式表和其他转换以使异构内容易于理解的编辑器也将变得无处不在。 最终,这些编辑器很可能会与程序编辑器合并,程序编辑器会实时交叉引用方法调用并评论编码风格。
但是,如果我们打算放弃平面 ASCII,为什么要用 XML 替换它呢? Microsoft .NET 中使用的 IL(中间语言)、Eclipse 的 AST 或编译器内部数据结构的序列化都值得推荐。 然而,程序员已经习惯于使用 XML,并且拥有强大的现成工具来操作它。 更重要的是,他们想要放入程序中的大多数其他信息都可以以 XML 形式获得。 就像 Inglish speling 一样,XML 将会继续存在。
安全性怎么样? 如果任何人都可以将新代码注入到编译/链接/调试工具链中,那么有什么可以阻止恶意(或不幸的)开发人员创建生成不安全或有害代码的东西呢? 答案是:“没有”。 当前关于证明编译器优化或源代码重构健全性的研究最终可能会提供字节码验证器为 Java 在运行时带来的那种安全性。 在那之前,用户将不得不小心他们下载和使用的内容——就像他们对待 Web 服务器扩展、Web 浏览器插件和游戏扩展包一样。
最后,语言功能以微妙的方式相互作用——如果每个有聪明想法的高中生都被允许向语言添加一个宠物功能,结果难道不会变得难以理解的胡言乱语吗?
您会问,“与什么相比?” 现在,程序员必须在脑海中解析几个函数调用才能理解一段 Java 代码正在尝试将字符串与正则表达式匹配,或者添加两个列表的相应元素。 运算符重载和其他类型的可扩展性可以使这些事情更容易理解(尽管某些 C++ 程序员的滥用证明了相反的情况也可能是真的);扩展可扩展性的范围最终可能会为我们提供并发、数据库访问等的可读符号。
大多数程序员不会构建扩展;他们会从其他人构建的扩展中挑选和选择。 由此产生的 “自由市场” 将使可读的扩展有机会击败不可读的扩展。 无法保证最好的会获胜(主要是因为让三位程序员在任何情况下就什么是最好的达成一致意见非常困难),但这种方法应该优于等待标准委员会达成共识,结果却发现他们又生产了一只骆驼。
为
垄断辩护
此处提出的更改可能会逐步发生:编辑器和编译器前端可能会首先出现,而下游工具稍后会赶上。 然而,在所有这些进步都到位之前,它们都不会产生充分的影响。 考虑到涉及的参与者数量,似乎不可能发生如此 “大爆炸” 的情况……
……除非,当然,一切都归单个供应商所有。 微软可以很容易地宣布 VB.NET 源文件将是 XML 文档;Wolfram Research 或 MathWorks 可以在 Mathematica 或 MATLAB 的下一个版本中做同样的事情,等等。
对于诸如 Mathematica 和 MATLAB 之类的数值语言,使用 XML 进行存储将允许程序员将真实的数学符号直接放入他们的源文件中。 对于 .NET,XML 存储将允许工具将元数据直接嵌入到代码中。 现在,平均 .NET 程序的一半是部署描述符、属性文件等等。 程序员需要在这些描述符中使用条件、for-each 迭代和重用,因此它们的表示法正在慢慢发展到包含真实编程语言所做的一切。 迟早,供应商将停止尝试用 “小语言” 解决问题,而开始在所有地方使用全强度解决方案。11
新的基于 XML 的语言已经出现(例如,Mozart、SuperX++、o:XML)12,但没有什么可以阻止开发 Java、Python 和 C# 的可扩展、基于 XML 的版本。 设计者已经在考虑将 XML 作为原始数据类型添加到这些语言中。13,14 允许程序员嵌入 XML 文档(而不是使用诸如 JavaDoc 之类的伪 HTML 黑客)将是自然的下一步,然后将其用于元数据(例如编译器指令)等等。
这些都是有用的创新,但它们不一定为程序员提供新的创新方式。 本文描述的更改将会做到这一点。 它们不会将每个新想法都视为特殊情况,而是让程序员在他们想说的时候、想说的地方、以他们想说的方式说出来。 结果将是同时更加复杂且更易于理解的系统。
当然,这一切都不是不可避免的。我们本可以仍然以每次一个字节的方式编写和查看程序,在命令行编译器中流式传输它们,诅咒我们的调试器难以驾驭,并抓耳挠腮地尝试将白板上潦草的明显含义翻译成嵌套的方法调用。但你真的相信这会发生吗?你真的相信我们的文档将是唯一没有标记、不能包含异构内容、不能被可扩展框架处理的文档吗? 阿尔文·托夫勒曾经说过,未来总是过早地以错误的顺序到来。作为过去 20 年输入了大量字节,并诅咒了很多调试器的人,我认为可扩展编程系统是一个不可能来得太早的未来。
1. Ladd, S. R. 2003. An evolutionary analysis of GNU C optimizations; 参见 http://www.coyotegulch.com/.
2. SUIF:参见 http://suif.stanford.edu/.
3. Bachrach, J., and K. Playford. 2001. The Java Syntactic Extender; 参见 http://www.ai.mit.edu/~jrb/jse/jse.pdf.
4. XDoclet:参见 http://xdoclet.sourceforge.net/.
5. Herrington, J. 2003. Code Generation in Action. Greenwich, CT: Manning.
6. Dollard, K. 2004. Code Generation in Microsoft .NET. Berkeley, CA: Apress.
7. Czarnecki, K., Eisenecker, U., Gluck, R. Vandevoorde, D., and Veldhuizen, T. L. 1998. Generative programming and active libraries. 在 Proceedings of Generic Programming ’98, Lecture Notes in Computer Science 1766, Springer-Verlag Telos.
8. Patzer, A. 2002. JSP Examples and Best Practices. Berkeley, CA: Apress.
9. 关于使用 Microsoft .NET 的 CodeDom 进行代码生成的优缺点的讨论,请参见参考文献 6(Dollard)。
10. Knuth, D. E. 1992. Literate Programming. Cambridge University Press.
11. 关于面向代码的构建工具的示例,请参见 CONS (http://www.dsmit.com/cons/) 和 SCons (http://www.scons.org/)。有趣的是,Ant 的创建者 James Duncan Davidson 写道:“如果我当时知道我现在所知道的,我会尝试使用真正的脚本语言,例如通过 Rhino 组件的 JavaScript 或通过 JPython 的 Python,并绑定到 Java 对象,这些对象实现了当今任务中表达的功能。那么,将有一种表达逻辑的一流方式,我们就不会被 XML 这种格式所困扰,XML 对于人们真正想要使用该工具的方式来说太笨重了。” http://x180.net/Articles/Java/AntAndXML.html。
12. Mozart:参见 http://mozart-dev.sourceforge.net/; SuperX++:参见 http://xplusplus.sourceforge.net/; o:XML:参见 http://www.o-xml.org.
13. ECMA-357:用于 XML 的 ECMAScript:参见 http://www.ecma-international.org/publications/standards/Ecma-357.htm.
14. Meijer, E. Schulte, W., 和 G. Bierman. Programming with circles, triangles, and rectangles; 参见 http://www.research.microsoft.com/~emeijer/Papers/XML2003/xml2003.html.
本文源于我早期为 Doctor Dobb’s Journal 撰写的一篇文章(“基于 XML 的编程系统”,2003 年 3 月)。我想感谢 Kimanzi Mati、Simon Peyton-Jones、Jim Larus、Mike Donat、Paul Prescod、Todd Veldhuizen、Mathew Zaleski 和 Mark Mitchell 对它的评论,以及 Ted Leung、Irving Reid、Charlie Reis、Dave Thomas、Matt Warren 和 Jim Maurer 对修订的评论。
喜欢它,讨厌它?请告诉我们
GREG WILSON 拥有爱丁堡大学计算机科学博士学位,曾在高性能科学计算、数据可视化、计算机安全和软件工程领域工作。他是 Practical Parallel Programming(MIT Press,1995)的作者,Doctor Dobb’s Journal 的特约编辑,以及多伦多大学计算机科学的兼职教授。
© 2004 1542-7730/04/1200 $5.00
最初发表于 Queue 杂志 第 2 卷 第 9 期—
在 数字图书馆 中评论这篇文章
Matt Godbolt - C++ 编译器中的优化
在给予编译器更多信息方面需要权衡:这可能会使编译变慢。诸如链接时优化之类的技术可以为您提供两全其美的好处。编译器中的优化在不断改进,即将到来的间接调用和虚函数分派方面的改进可能很快就会带来更快的多态性。
Ulan Degenbaev, Michael Lippautz, Hannes Payer - 作为合资企业的垃圾回收
跨组件跟踪是解决跨组件边界的引用循环问题的一种方法。只要组件可以形成任意对象图,并在 API 边界上具有重要的所有权,就会出现此问题。 CCT 的增量版本已在 V8 和 Blink 中实现,从而能够以安全的方式有效且高效地回收内存。
David Chisnall - C 语言不是一种低级语言
在最近的 Meltdown 和 Spectre 漏洞之后,值得花一些时间来研究根本原因。这两种漏洞都涉及处理器推测性地执行超出某种访问检查的指令,并允许攻击者通过侧信道观察结果。导致这些漏洞以及其他几个漏洞的功能的添加,是为了让 C 程序员继续相信他们正在使用低级语言进行编程,而这种情况已经几十年没有发生过了。
Tobias Lauinger, Abdelberi Chaabane, Christo Wilson - 汝不可依赖于我
大多数网站都使用 JavaScript 库,其中许多库已知是易受攻击的。了解问题的范围,以及包含库的许多意外方式,只是改善情况的第一步。 此处的目标是本文中包含的信息将有助于为社区提供更好的工具、开发实践和教育工作。