关于语言设计的五个问题
2001年5月
(这些是我为2001年5月10日在MIT举行的编程语言设计小组讨论所做的笔记。)
1. 编程语言是为人设计的。
编程语言是人们与计算机交流的方式。只要有歧义性,计算机本身对使用哪种语言并不在意。我们之所以有高级语言,是因为人无法处理机器语言。编程语言的意义在于防止我们脆弱的人类大脑被大量细节压垮。
建筑师知道某些设计问题比其他问题更具个人色彩。最干净、最抽象的设计问题之一是设计桥梁。在那里,你的工作主要是用最少的材料跨越给定的距离。而另一端则是设计椅子。椅子设计师必须花时间思考人的臀部。
软件也同样如此。设计用于网络数据路由的算法是一个很好的抽象问题,就像设计桥梁一样。而设计编程语言则类似于设计椅子:它完全关乎如何处理人类的弱点。
我们大多数人讨厌承认这一点。设计出数学上优雅的系统,对我们大多数人来说,听起来比迎合人类弱点要吸引人得多。而数学优雅确实有作用:某些类型的优雅能让程序更容易理解。但优雅本身并不是目的。
当我说语言必须设计以适应人类的弱点时,我并不是说语言必须为糟糕的程序员设计。事实上,我认为你应该为最优秀的程序员设计,但即使是最优秀的程序员也有局限。我不认为有人会喜欢用所有变量都是带整数下标的字母x这样的语言来编程。
2. 为你自己和你的朋友设计。
如果你回顾编程语言的历史,许多最好的语言都是为其作者自己使用而设计的,而许多最糟糕的语言则是为其他人使用而设计的。
当语言为他人设计时,总是指一个特定的群体:那些不如语言设计者聪明的人。于是你得到一种居高临下的语言。Cobol是最极端的例子,但许多语言都弥漫着这种精神。
这与语言的抽象程度无关。C相当底层,但它是为作者自己使用而设计的,这就是黑客喜欢它的原因。
为糟糕的程序员设计语言的理由是,糟糕程序员比优秀程序员多。这可能没错。但那些少数优秀程序员写了不成比例的大量软件。
我感兴趣的问题是,如何设计一种最优秀的黑客会喜欢的语言?我碰巧认为这与“如何设计一门好的编程语言?”是同一个问题,但即使不是,它至少也是一个有趣的问题。
3. 尽可能给程序员更多控制权。
许多语言(尤其是为他人设计的语言)有一种家庭教师的态度:它们试图阻止你做一些它们认为对你没好处的事情。我喜欢相反的方法:尽可能给程序员更多的控制权。
当我第一次学习Lisp时,我最喜欢的一点是它把我视为平等的伙伴。在直到那时我学过的其他语言中,有语言和我的程序——用该语言编写的程序——两者是分开的。但在Lisp中,我编写的函数和宏与构成语言本身的那些完全一样。如果我愿意,我可以重写这门语言。它有着和开源软件一样的吸引力。
4. 力求简洁。
简洁被低估甚至蔑视。但如果你深入黑客的内心,你会发现他们真的很喜欢它。你听过多少次黑客深情地提起,在比如APL中,他们可以用几行代码就做出惊人的事情?我认为任何真正聪明的人真正喜欢的东西都值得关注。
我认为几乎任何能让程序更短的事情都是好的。应该有大量的库函数;任何可以隐式的东西都应该是隐式的;语法应该简洁到极致;甚至事物的名称也应该简短。
而且不仅程序应该短。手册也应该薄。手册的很大一部分内容都是澄清、保留、警告和特殊情况。如果你强迫自己缩短手册,最好的情况是你通过修复语言中需要这么多解释的部分来实现。
5. 承认黑客的本真。
很多人希望黑客是数学,或至少像自然科学一样。我认为黑客更像建筑学。建筑学与物理学有关,因为建筑师必须设计不会倒塌的建筑,但建筑师的实际目标是建造伟大的建筑,而不是发现静力学原理。
黑客喜欢做的是编写伟大的程序。而且我认为,至少在我们自己心里,我们必须记住编写伟大程序是值得钦佩的事情,即使这项工作不容易转化为传统意义上的研究论文智力货币。从智力上讲,设计一种程序员会喜欢的语言,与设计一种糟糕的语言但体现某种可以发表论文的想法,同样值得。
1. 如何组织大型库?
库正成为编程语言中越来越重要的组成部分。它们也在变大,这可能很危险。如果找到能满足你需求的库函数花费的时间比你自己编写还长,那么所有那些代码只不过让你的手册变厚而已。(Symbolics的手册就是一个例子。)所以我认为我们必须想办法组织库。理想情况下,应该设计它们,以便程序员能猜到哪个库调用会做正确的事情。
2. 人们真的害怕前缀语法吗?
这是一个开放性问题,因为我已经思考多年,仍然不知道答案。前缀语法对我来说似乎完全自然,除了可能数学除外。但可能是Lisp不受欢迎的很大一部分原因仅仅是因为语法不熟悉。如果是真的,是否应该对此做些什么,是另一个问题。
3. 服务器端软件需要什么?
我认为未来二十年写出的许多最令人兴奋的新应用将是基于Web的应用,即坐在服务器上并通过Web浏览器与你对话的程序。为了编写这些类型的程序,我们可能需要一些新东西。
我们需要的一件事是支持服务器端应用发布的新方式。不同于桌面软件每年发布一两次大版本,服务器端应用以一系列小更改的形式发布。你可能每天有五到十次发布。而且通常每个人总是使用最新版本。
你知道如何设计程序使其可调试吗?同样,服务器端软件也必须设计成可更改的。你必须能够轻松更改它,或者至少知道什么是小更改,什么是重大更改。
另一个可能对服务器端软件有用的事物,出乎意料的是,连续体。在基于Web的软件中,你可以使用类似连续传递风格的东西,在Web会话固有的无状态世界中获得子程序的效果。也许真正拥有连续体会是值得的,如果成本不太高的话。
4. 还有哪些新抽象有待发现?
我不确定这是否是一个合理的希望,但个人而言,我非常想做的一件事是发现一个新的抽象——一个能像拥有头等函数、递归或甚至关键字参数那样产生巨大差异的东西。这可能是一个不可能的梦想。这些事物不常被发现。但我一直在寻找。
1. 你可以使用你想要的任何语言。
编写应用软件过去意味着编写桌面软件。而在桌面软件中,有一种强烈的倾向,即用与操作系统相同的语言编写应用程序。因此,十年前,编写软件几乎意味着用C编写。最终形成了一种传统:应用程序不能使用不常见的语言。这个传统发展得如此之长,以至于像经理和风险投资家这样的非技术人也学会了它。
服务器端软件彻底打破了整个模式。有了服务器端软件,你可以使用你想要的任何语言。几乎没有人理解这一点(尤其是经理和风险投资家)。少数黑客理解它,这就是为什么我们甚至听说过像Perl和Python这样的新独立语言。我们不是因为人们用它们编写Windows应用才听说Perl和Python的。
这对我们这些对设计编程语言感兴趣的人来说意味着,现在我们的工作潜在地有了真正的受众。
2. 速度来自性能分析器。
语言设计者,或者至少语言实现者,喜欢编写生成快速代码的编译器。但我认为这不是让用户感觉语言快速的原因。Knuth很久以前就指出,速度只在少数关键瓶颈中才重要。而且任何尝试过的人都知道,你无法猜测这些瓶颈在哪里。性能分析器才是答案。
语言设计者正在解决错误的问题。用户不需要基准测试快速运行。他们需要的是一种能够展示他们自己程序的哪些部分需要重写的语言。这才是实践中的速度来源。所以,如果语言实现者把花在编译器优化上的时间的一半用来编写一个好的性能分析器,那可能会是净收益。
3. 你需要一个应用程序来推动语言设计。
这可能不是绝对规则,但似乎最好的语言都是与它们被用来编写的某个应用程序一起演化的。C是由需要它进行系统编程的人编写的。Lisp部分是为了做符号微分而开发的,麦卡锡如此急于开始,以至于即使在1960年的第一篇关于Lisp的论文中,他也已经在编写微分程序了。
如果你的应用程序解决了某个新问题,那就特别好。那往往会推动你的语言拥有程序员需要的新特性。我个人对编写一种适合编写服务器端应用的语言感兴趣。
[在小组成员讨论期间,盖伊·斯蒂尔也提出了这一点,并额外建议,应用程序不应包括编写你的语言的编译器,除非你的语言恰好是为了编写编译器而设计的。]
4. 一门语言必须适合编写一次性程序。
你知道什么是一次性程序:为某些有限任务快速编写的东西。我认为如果你四处看看,你会发现许多大型、严肃的程序都是从一次性程序开始的。如果大多数程序都是一次性程序起步,我不会感到惊讶。因此,如果你想制作一种好的通用软件编写语言,它必须适合编写一次性程序,因为那是大多数软件的幼虫阶段。
5. 语法与语义相关联。
传统上认为语法和语义是完全分开的。这听起来可能令人震惊,但它们可能并非如此。我认为你希望你的语言具有的特性可能与你的表达方式相关。
最近,我与罗伯特·莫里斯交谈时,他指出,在中缀语法语言中,操作符重载优势更为显著。而在前缀语法语言里,任何你定义的函数实质上就是一个操作符。若想为自创的新型数字类型定义加法,只需定义一个相加函数即可。但在中缀语法语言中,重载操作符的使用与函数调用在外观上存在显著差异。
1. 新编程语言。
回溯至20世纪70年代,设计新编程语言曾风靡一时。而近年来,这一趋势已不如前。但我认为,基于服务器的软件将使新编程语言再次成为焦点。借助服务器端软件,你可以选用任何心仪的语言,因此,若有人设计出一种确实优于现有选项的语言,定会有人愿意冒险尝试。
2. 分时系统。
理查德·凯尔西在上次专题讨论中提出,分时理念正逢其时,我完全赞同。据我猜测(微软似乎也持相同看法),大量计算任务将从桌面端迁移至远程服务器,换言之,分时系统将卷土重来。我认为,在语言层面需为其提供支持,例如,我知道理查德和乔纳森·里斯在Scheme 48中已大量投入于进程调度的实现。
3. 效率。
近来,似乎电脑终于足够快了。我们越来越多地听到关于字节码的讨论,这至少让我觉得,我们已感到计算资源充裕。但对于服务器端软件,情况或许并非如此。软件运行依赖的服务器需有人买单,而每台机器能支撑的用户数量,将直接决定其资本成本的摊分。
因此,我认为效率将变得至关重要,至少在处理计算瓶颈时如此。尤其在处理输入输出上需高速,因为服务器端应用涉及大量I/O操作。
最终,字节码可能并非上策。目前,Sun与微软似乎正围绕字节码展开较量,但此举更多是因为字节码便于他们介入流程,而非字节码本身是良策。或许,整个战场都会被绕过,那将颇为有趣。
1. 客户端。
这纯属猜测,但我预感多数应用的最佳模式将纯然基于服务器。假设所有用户都安装你的客户端来设计软件,犹如假设人人皆诚实的理想社会,虽便捷无比,但现实往往背道而驰。
我认为,各种支持网页访问的设备将层出不穷,而你能对这些设备的假设仅限于它们能处理简易HTML和表单。手机上有浏览器?掌上电脑内置电话?黑莓有更大屏幕?游戏机或手表能上网?我一无所知。但只要押注于一切皆在服务器端,便无需预知答案。将全部“智慧”集中在服务器端,无疑更为稳健。
2. 面向对象编程。
我明白这是争议话题,但我并不认为面向对象编程有多大的玄机。对于需要特定数据结构(如窗口系统、模拟、CAD程序)的某些应用,它不失为一种好模型。但我看不出它为何应成为所有编程的通用模型。
大公司青睐面向对象编程的部分原因,或许在于它产出了大量看似工作量的东西。原本可能简单表示为整数列表的事物,现在却能包裹成类,附带各种框架与喧嚣。
面向对象编程的另一吸引力在于,方法提供了部分一级函数的效果。但这对Lisp程序员而言已是旧闻。当你真正拥有第一类函数时,可根据手头任务灵活运用,而非硬将一切都套入类与方法的桎梏中。
这对语言设计意味着,我认为不应过度内置面向对象编程。或许,提供更通用、底层的部件,让人们按需设计自己的对象系统作为库,才是答案。
3. 委员会设计。
由委员会设计语言是个大坑,不仅因为众所周知的理由。人们皆知,委员会倾向于产出臃肿、不一致的设计。但我认为更大风险在于,他们不愿承担风险。当一人掌舵时,他可以冒险尝试委员会绝不会一致同意的决定。
然而,设计一门好语言是否必须冒险?许多人可能会认为,语言设计应贴近传统智慧。我敢打赌事实并非如此。在人类所有活动中,回报与风险成正比。语言设计为何应例外?