百年语言
Original2003年4月
(本文转自2003年PyCon大会的主题演讲。)
很难预测100年后的生活会是什么样子。我们只能确定一些事情。我们知道每个人都会开飞行汽车,城市规划法规将放松以允许建造上百层的高楼,大部分时间都会很黑暗,所有女性都将接受武术训练。我想着重探讨其中的一个细节:100年后他们会用什么样的编程语言来编写那些飞行汽车的控制软件?
这个问题值得思考,不仅是因为我们可能会亲身使用这些语言,更是因为如果我们幸运的话,我们现在使用的语言会是通往那个未来的路径之一。
我认为,就像物种一样,语言也会形成进化树,到处都有死胡同分支。我们已经可以看到这种情况正在发生。COBOL虽然曾经很流行,但似乎没有任何智力后代。它是一种进化死胡同——一种类似尼安德特人的语言。
我预测Java也会遭遇类似的命运。有人会给我发邮件说:"你怎么能说Java不会成为一种成功的语言?它已经是一种成功的语言了。"我承认它的确很成功,如果你用书架上的书的数量(特别是关于它的单本书)或相信必须学习它才能找到工作的大学生的数量来衡量成功的话。但当我说Java不会成为一种成功的语言时,我的意思是更具体的:Java会最终成为一种进化的死胡同,就像COBOL一样。
这只是一个猜测。我可能会错。我在这里的重点不是贬低Java,而是要提出进化树的问题,让人们思考语言X位于哪个位置?问这个问题的原因不仅仅是为了让我们的鬼魂在100年后说"我就是这么说的"。它还因为保持在主要分支附近是一个非常有用的启发式方法来寻找现在编程中很棒的语言。
在任何给定的时间,你可能都更喜欢在进化树的主要分支上。即使尼安德特人还很多,成为一个也不太好受。克罗马侬人会不断过来打你,抢你的食物。
我想知道100年后语言会是什么样子,是因为我想知道现在应该押注哪个分支。
编程语言的进化与物种的进化不同,因为分支可以收敛。例如,Fortran分支似乎正在与Algol的后代融合。理论上,这种情况也可能发生在更大的物种中,但这种情况很不可能发生。
分支收敛对语言来说更容易发生,部分是因为可能性的空间更小,部分是因为突变并非随机。语言设计者有意识地吸收了其他语言的思想。
对于语言设计者来说,思考编程语言的进化将走向何方是特别有用的,因为他们可以相应地进行调整。在这种情况下,"保持在主要分支上"不仅仅是选择一种好语言的方法,它还成为做出正确语言设计决策的启发式方法。
任何编程语言都可以分为两部分:某些基本操作符,充当公理的角色,以及可以用这些基本操作符来表述的其余语言部分。
我认为,基本操作符是决定一种语言长期生存的最重要因素。其他部分你可以改变。这就像买房时要首先考虑位置的原则。其他一切都可以之后再修复,但位置是无法改变的。
我认为,不仅公理必须被很好地选择,而且数量也应该很少。数学家一直认为公理越少越好,我认为他们说得很有道理。
至少,仔细检查一门语言的核心,看看是否有任何公理是可以去除的,这是一个很有意义的练习。在我冗长的懒汉生涯中,我发现陈旧会滋生陈旧,在软件领域也是如此。
我有一种直觉,主要进化分支会经过那些核心最小、最干净的语言。能够自身写出的语言部分越多,就越好。
当然,我在问100年后编程语言会是什么样子时,我做了一个很大的假设。我们100年后还会用编程的方式与计算机交互吗?我们不会直接告诉计算机我们想要它做什么吗?
到目前为止,在这方面并没有太大进展。我猜测100年后,人们仍然会用我们今天认识的程序的方式来告诉计算机该做什么。可能会有一些我们现在通过编程来解决的任务,到那时就不需要编程就能解决,但我认为仍然会有大量我们今天做的那种编程工作。
预测任何技术在100年后会是什么样子,可能看起来很自负。但请记住,我们已经有近50年的历史作为借鉴。考虑到过去50年编程语言的发展速度,展望100年并不是一个难以掌握的想法。
语言进化缓慢,因为它们并不是真正的技术。语言是符号。程序是你想让计算机为你解决问题的正式描述。所以编程语言的进化速度更像数学符号的进化速度,而不是交通或通讯技术的进化速度。数学符号确实在进化,但不会出现技术领域那样的巨大飞跃。
不管一百年后计算机由什么制成,可以安全地预测它们会比现在快得多。如果摩尔定律继续有效,它们会快73,786,976,294,838,206,464倍。这有点难以想象。事实上,速度方面最可能的预测可能是摩尔定律会停止起作用。任何每18个月就要翻一番的东西,最终似乎都会遇到某种基本的限制。但我毫不怀疑计算机会大大加快速度。即使它们只最终快上百万倍,也应该会极大地改变编程语言的基本规则。
不过,有些应用仍然需要速度。有些我们想用计算机解决的问题是由计算机本身创造的;例如,你必须处理视频图像的速率取决于另一台计算机可以生成它们的速率。还有一类本质上可以吸收无限循环的问题:图像渲染、密码学、模拟。
如果有些应用可以越来越低效,而其他应用仍然需要尽可能快的硬件,那么更快的计算机将意味着语言必须覆盖越来越广泛的效率范围。我们已经看到这种情况在发生。一些新语言的当前实现在过去几十年的标准看来都令人震惊地浪费。
这不仅仅发生在编程语言上。这是一般的历史趋势。随着技术的进步,每一代人都能做一些上一代人会认为是浪费的事情。30年前的人会对我们现在如此随意地打长途电话感到惊讶。100年前的人会更惊讶,一个包裹可以从波士顿经孟菲斯到达纽约。
我可以告诉你在未来100年更快的硬件带来的所有额外循环将会如何被浪费。它们几乎全都会被浪费掉。
我在计算机资源稀缺时学会编程。我还记得从我的Basic程序里删除所有空格,好让它们装进TRS-80的4K内存。看到如此惊人低效的软件一次又一次地占用循环,让我觉得有些恶心。但我想我的直觉在这里是错的。我就像一个从贫穷中长大的人,即使是为了重要的事情,比如看医生,也无法轻易花钱。
有些浪费确实是令人反感的。SUV就是一个例子,即使它们使用永不枯竭且不产生污染的燃料,也仍然令人反感。SUV之所以令人反感,是因为它们是解决一个令人反感问题的办法(如何让迷你货车看起来更有男子气概)。
但并非所有的浪费都是坏事。现在我们有了支持它的基础设施,开始计算长途电话的分钟数似乎有些刻板。如果你有资源,把所有电话都视为一种事物,无论对方在哪里,会更优雅一些。
有好的浪费,也有坏的浪费。我对好的浪费感兴趣——通过投入更多资源,我们可以获得更简单的设计。我们将如何利用新的更快硬件带来的浪费机会呢?
对速度的追求深植于我们这些拥有微不足道计算机的人心中,我们需要有意识地克服它。在语言设计中,我们应该有意识地寻找那些可以以效率为代价换取哪怕是微小方便的情况。
大多数数据结构的存在都是为了追求速度。例如,许多语言今天同时拥有字符串和列表。从语义上来说,字符串基本上是元素为字符的列表的子集。那么为什么需要一种单独的数据类型呢?实际上并不需要。字符串只存在是为了效率。但是用些许技巧来使程序运行得更快来污染语言的语义是很可笑的。
如果我们把语言的核心看作一组公理,那么仅为了效率而增加不增加任何表达能力的额外公理,似乎是很恶心的。效率很重要,但我认为这不是获得它的正确方式。
我认为解决这个问题的正确方式是将程序的含义与实现细节分开。不要同时拥有列表和字符串,只有列表,并提供一些编译器优化建议,允许它在必要时将字符串以连续字节的形式布局。
由于大部分程序中速度并不重要,通常你无需费心处理这种细节管理。随着计算机变得越来越快,这将越来越成立。
说得越少关于实现的细节,程序也应该会变得更加灵活。规格在编写过程中会发生变化,这不仅是不可避免的,而且也是可取的。
"essay"一词源自法语动词"essayer",意思是"尝试"。按最初的意义,essay是你写下来试图弄清楚某事的东西。在软件中也是如此。我认为一些最好的程序就是essay,意味着作者在开始时并不完全知道自己要写什么。
Lisp黑客已经了解到灵活使用数据结构的价值。我们倾向于编写第一版程序,使其完全依靠列表。这些初始版本可能效率低得令人震惊,就像对我来说,吃牛排需要有意识地不去想它的来源一样。
一百年后的程序员最需要的,是一种可以以最小的努力构建出令人难以置信低效的版本1程序的语言。至少用现在的术语来说是这样。他们会说,他们想要一种编程起来很容易的语言。
低效的软件并不令人反感。令人反感的是一种语言,它使程序员做不必要的工作。浪费程序员时间才是真正的低效,而不是浪费机器时间。随着计算机变得越来越快,这一点将越来越明显。
我认为摆脱字符串已经是我们可以考虑的事情了。我们在Arc中做到了这一点,这似乎是个收益;有些操作如果用正则表达式来描述会很别扭,但用递归函数来描述就很容易。
这种数据结构的简化会进行到什么程度呢?我能想到一些会让我震惊的可能性,即便我的思维已经被有意识地拓宽了。我们会摆脱数组吗?毕竟,它们不过是键为整数向量的哈希表的一个子集。我们会用列表取代哈希表本身吗?
还有更令人震惊的前景。比如,麦卡锡在1960年描述的Lisp根本没有数字。从逻辑上讲,你不需要对数字有单独的概念,因为你可以用列表来表示它们:整数n可以表示为一个有n个元素的列表。你可以用这种方式进行数学运算。只是效率实在是太低了。
事实上,没有人真的建议在实践中用列表来表示数字。麦卡锡的1960年论文当时根本就不是为了实现而写的。那是一次理论练习,试图创造一种比图灵机更优雅的替代品。当有人出乎意料地把这篇论文翻译成一个可用的Lisp解释器时,数字当然不会用列表来表示,而是用二进制来表示,就像其他任何语言一样。
一种编程语言会彻底摆脱数字这种基本数据类型吗?我问这个问题不是把它当成一个认真的问题,而是把它当成是与未来的一种玩鸡毛蒜皮的方式。这就像一个无法抗拒的力量遇到一个无法移动的物体-这里,一种难以想象的低效实现遇到了难以想象的巨大资源。我看不出为什么不可以。未来很漫长。如果我们能做些什么来减少核心语言中的公理数量,那似乎就是我们应该押注的方向,当t趋向于无穷大时。如果这个想法在一百年后还令人无法接受,或许在一千年后就不会了。
再说明一下,我并不是建议所有的数值计算都用列表来进行。我是在建议,在任何额外的关于实现的符号之前,核心语言就应该被这样定义。实际上,任何想要进行任何数学运算的程序都可能会用二进制来表示数字,但这只是一种优化,而不是核心语言语义的一部分。
另一种浪费时间的方式是在应用程序和硬件之间设置多层软件。我们已经看到这种趋势正在发生:许多最近的语言都被编译成字节码。比尔·伍兹曾经告诉我,作为一个经验法则,每一层解释都会使速度慢10倍。这种额外的成本为您带来了灵活性。
Arc的第一个版本是这种多层次迟缓的一个极端案例,伴随着相应的好处。它是一个经典的"元循环"解释器,构建在Common Lisp之上,与麦卡锡最初的Lisp论文中定义的eval函数有很强的家族关系。整个程序只有几百行代码,所以很容易理解和修改。我们使用的Common Lisp-CLisp本身就运行在一个字节码解释器之上。所以我们在这里有两层解释,其中一层(顶层)效率极低,而这种语言还是可用的。我承认,勉强可用,但总之还是可用的。
在应用程序内部,将软件写成多层也是一种强大的技术。自下而上的编程意味着将一个程序编写成一系列层,每一层都作为上一层的语言。这种方法往往会产生更小、更灵活的程序。这也是实现可重用性这个神圣目标的最佳途径。从定义上说,一种语言是可重用的。你能将应用程序中的越多部分推到为编写该类应用程序的语言中去,你的软件就会越可重用。
在20世纪80年代,可重用性这个想法似乎与面向对象编程绑定在一起,任何相反的证据都无法将其分离开来。但是,尽管一些面向对象的软件是可重用的,但使它可重用的是它的自下而上的特性,而不是它的面向对象特性。以库为例:它们之所以可重用,是因为它们就是一种语言,无论它们是用面向对象的方式编写的还是不是。
顺便说一下,我并不预测面向对象编程会消亡。尽管我认为它对于优秀的程序员来说没有太多可取之处,除了在某些专门的领域,但它对于大型组织来说是不可抗拒的。面向对象编程为编写意大利面条式代码提供了一种可持续的方式。它让你可以以一系列补丁的形式累加程序。
大型组织总是倾向于用这种方式开发软件,我预计这在一百年后仍将如此。
既然我们在谈论未来,我们最好也谈谈并行计算,因为这似乎是这个想法的所在之地。也就是说,不管何时谈论,并行计算似乎都是将来要发生的事情。
未来会追上它吗?人们已经至少20年一直在谈论并行计算是即将到来的东西,但到目前为止它还没有对编程实践产生太大影响。或者说,它已经产生了影响吗?芯片设计师已经不得不考虑它,编写多CPU计算机系统软件的人也必须考虑它。
真正的问题是,并行性会上升到多高的抽象层次?在100年后,它会影响到应用程序员吗?还是它只会是编译器编写者要考虑的东西,但通常在应用程序的源代码中是不可见的?
有一点似乎很可能,那就是大部分并行性的机会都会被浪费掉。这是我更普遍的一个预测-我们获得的大部分额外计算能力都会被浪费掉。我预计,就像底层硬件的惊人速度一样,并行性也将是一种需要你明确要求的东西,否则通常不会被使用。这意味着,在100年后,我们所拥有的并行性不会是大规模并行,除了一些特殊应用之外。我预计,对于普通程序员来说,它更像是能够分叉出并行运行的进程。
这种要求特定的数据结构实现也会在程序生命周期的后期才出现,当你试图优化它时。初版通常会忽略并行计算的优势,就像它们会忽略特定数据表示的优势一样。
除了特殊类型的应用程序,并行计算在未来100年内不会渗透到编写的程序中。这将是一种过早的优化。
100年后会有多少种编程语言?最近出现了大量新的编程语言。其中一部分原因是更快的硬件让程序员能够根据不同的应用程序在速度和便利性之间做出不同的权衡。如果这是一种真正的趋势,那么100年后我们将拥有的硬件只会加剧这一趋势。
然而,100年后可能只会有几种广泛使用的语言。我之所以这么说,是出于乐观:如果你做得很好,你可以创造一种语言,它非常适合编写慢速版本1,但通过向编译器提供正确的优化建议,在必要时也能产生非常快的代码。所以,既然我很乐观,我预测,尽管程序员在100年后会在可接受性能和最大性能之间存在巨大差距,但他们仍将拥有可以覆盖其中大部分的语言。
随着这种差距的扩大,剖析器将变得越来越重要。现在很少关注剖析。许多人似乎仍然认为,获得快速应用程序的方法是编写产生快速代码的编译器。随着可接受性能和最大性能之间的差距不断扩大,人们将越来越明白,获得快速应用程序的方法是拥有从一种性能到另一种性能的良好指南。
当我说可能只有几种语言时,我并不包括特定领域的"小语言"。我认为这种嵌入式语言是一个很好的想法,并且我预计它们会越来越多。但我希望它们能够足够薄,让用户能看到下面的通用语言。
谁将设计未来的语言?过去10年中最令人兴奋的趋势之一是开源语言如Perl、Python和Ruby的兴起。语言设计正被黑客接管。到目前为止的结果是混乱的,但令人鼓舞。Perl中有一些令人惊叹的新思想。许多思想令人震惊,但这对于雄心勃勃的努力来说总是如此。以Perl目前的变异速度,谁知道它在100年后会演变成什么样。
"不会做的人去教书"并不完全正确(我认识的一些最优秀的黑客都是教授),但确实存在那些教书的人做不了某些事情的情况。研究强加了限制性的等级制度。在任何学术领域,都有可以研究的话题和不可以研究的话题。不幸的是,可以研究和不可以研究的区别通常是基于工作在研究论文中描述起来有多智性,而不是它对获得良好结果有多重要。这种情况在文学研究领域可能最为极端;研究文学的人很少会说任何对创作文学有用的话。
尽管科学领域的情况有所好转,但您被允许从事的工作类型与产生好的语言所需的工作类型之间的重叠令人沮丧地很小。(Olin Shivers曾经抱怨过这一点。)例如,类型似乎是不尽人意的研究论文来源,尽管静态类型似乎排除了真正的宏观--在我看来,没有宏观的语言是不值得使用的。
这一趋势不仅体现在语言作为开源项目而不是"研究"来开发,而且体现在语言由需要使用它们的应用程序程序员而不是编译器编写器来设计。这似乎是一个好的趋势,我希望它会继续下去。
与100年后的物理学不同,物理学几乎无法预测,我认为,从原理上讲,现在就有可能设计出一种语言,在100年后仍会吸引用户。
设计语言的一种方法是,不考虑是否有编译器可以翻译它或硬件可以运行它,而只是写下您希望能够编写的程序。当您这样做时,您可以假设拥有无限的资源。似乎我们今天应该能够像100年后一样想象无限的资源。
人们想要编写什么样的程序?尽可能少的工作。不完全是这样:如果你的编程思想不受你目前使用的语言的影响,那么你会认为什么样的程序需要最少的工作。这种影响是如此广泛,以至于需要很大的努力才能克服它。你会认为,对于我们这些懒惰的生物来说,用最少的努力表达一个程序应该是很明显的。事实上,我们对可能性的想法往往被我们所思考的语言所局限,以至于程序的更容易表达的形式似乎非常令人惊讶。这需要发现,而不是自然而然地产生。
这里有一个有帮助的技巧是,使用程序的长度作为编写工作量的近似值。当然,不是字符长度,而是不同语法元素的长度--基本上就是解析树的大小。 编写最短的程序可能并不完全等同于最少的工作量,但它足够接近,以至于你最好瞄准简洁性这个明确的目标,而不是模糊接近的最少工作量。然后,语言设计的算法就变成了:看一个程序,问一下,有没有更简短的写法?
在实践中,根据离核心的远近程度,在想象中的100年语言中编写程序将效果各不相同。现有的排序程序你现在就可以编写。但很难预测100年后可能需要什么样的库。presumably 许多库将为目前还不存在的领域服务。例如,如果SETI@home有效,我们将需要与外星人通信的库。当然,除非他们足够先进,已经用XML进行通信。
在另一个极端,我认为你可能今天就能设计出核心语言。事实上,有人可能会论证,它在1958年就基本设计好了。
如果百年语言现在可用,我们会想用它编程吗?回答这个问题的一种方法是回顾过去。如果现有的编程语言在1960年就已经存在,人们会想使用它们吗?
在某些方面,答案是否定的。今天的语言假定了1960年还不存在的基础设施。例如,像Python一样以缩进为语法的语言,在打印终端上并不太适用。但是,如果忽略这些问题,假设所有程序都是手写的,1960年代的程序员会喜欢用我们现在使用的语言编程吗?
我认为会。一些缺乏想象力的人,他们对早期语言的特点已深入骨髓,可能会有困难。(没有指针运算如何处理数据?没有goto如何实现流程图?)但我认为最聪明的程序员完全可以充分利用现代语言,如果他们当时就有这样的机会。
如果我们现在就有百年语言,它至少可以用作很棒的伪代码。那么用它来编写软件呢?既然百年语言需要为某些应用生成高效的代码,presumably它也能生成足以在我们的硬件上运行得可接受的代码。我们可能需要提供比未来百年用户更多的优化建议,但这也许总体上是有益的。
现在我们有两个想法:1) 百年语言理论上可以在今天设计出来,2) 如果存在这样的语言,可能今天就很适合用它编程。把这两个想法联系起来,不禁会想,为什么不现在就尝试编写百年语言呢?
在设计语言时,我认为设定这样一个目标并时刻牢记是很有帮助的。学习开车时,他们教导你不是对准道路上的标线来对准车头,而是瞄准远处的一个点。即使你只关心接下来10英尺的情况,这也是正确的做法。我认为我们在设计编程语言时也应该采取同样的方法。
备注
我相信Lisp Machine Lisp是第一个体现"声明(除了动态变量声明)只是优化建议,不会改变正确程序的含义"这一原则的语言。Common Lisp似乎是第一个明确阐述这一点的语言。
感谢 Trevor Blackwell、Robert Morris和Dan Giffin阅读了本文的初稿,感谢Guido van Rossum、Jeremy Hylton以及Python团队的其他成员邀请我在PyCon上发表演讲。