百年语言

2003年4月

(本文源自2003年PyCon大会的主题演讲。)

很难预测一百年后的生活会是什么样子。我们只能确定几件事。我们知道每个人都会驾驶飞行汽车,分区法会放宽以允许建造数百层高的建筑物,大部分时间将是黑暗的,而且所有女性都将接受武术训练。在这里,我想聚焦于这幅图景中的一个细节。他们将使用什么样的编程语言来编写控制这些飞行汽车的软件?

思考这个问题很有价值,倒不是因为我们真的会使用这些语言,而是因为,如果我们幸运的话,我们将使用从现在到那时这段路径上的语言。

我认为,像物种一样,语言会形成进化树,到处都有死胡同分支。我们已经可以看到这种情况正在发生。Cobol,尽管曾经很流行,但似乎没有任何智力上的后代。它是一个进化的死胡同——一种尼安德特人语言。

我预测Java也会有类似的命运。人们有时会给我发邮件说:“你怎么能说Java不会成为一种成功的语言?它已经是一种成功的语言了。”我承认,如果你用书架上关于它的书(特别是关于它的个别书籍)所占据的空间,或者用认为他们必须学习它才能找到工作的本科生的数量来衡量成功,那么它确实是成功的。当我说Java不会成为一种成功的语言时,我的意思是更具体的东西:Java将变成一个进化的死胡同,就像Cobol一样。

这只是一个猜测。我可能是错的。我在这里的重点不是贬低Java,而是提出进化树的问题,并让人们问,语言X在树上的哪个位置?提出这个问题的原因不仅仅是为了让我们的幽灵在一百年后说,我告诉过你。而是因为,保持靠近主要分支是找到现在适合编程的语言的有用启发式方法。

在任何给定的时间,你可能在进化树的主要分支上最快乐。即使当时还有很多尼安德特人,当一个尼安德特人也一定很糟糕。克罗马农人会不断地过来殴打你并偷走你的食物。

我想知道一百年后语言会是什么样子,这样我就知道现在该押注树的哪个分支。

语言的进化与物种的进化不同,因为分支可以收敛。例如,Fortran分支似乎正在与Algol的后代合并。理论上,这对物种来说也是可能的,但它不太可能发生在比细胞更大的生物身上。

语言更容易收敛,部分原因是可能性的空间较小,部分原因是突变不是随机的。语言设计者会刻意地从其他语言中吸收想法。

对于语言设计者来说,思考编程语言的进化可能导致的方向尤其有用,因为他们可以相应地进行引导。在这种情况下,“留在主要分支上”不仅仅是选择一种好的语言的方式。它成为做出关于语言设计的正确决策的启发式方法。

任何编程语言都可以分为两部分:一些充当公理的基本运算符集合,以及语言的其余部分,原则上可以用这些基本运算符来编写。

我认为基本运算符是语言长期生存的最重要因素。其余的你可以改变。这就像买房子时应该首先考虑位置的规则一样。其他一切你以后都可以修复,但你无法修复位置。

我认为不仅要精心选择公理很重要,而且公理的数量要少。数学家一直都这样看待公理——越少越好——我认为他们说对了。

至少,仔细观察语言的核心,看看是否有任何可以剔除的公理,这应该是一个有用的练习。在我作为邋遢鬼的漫长职业生涯中,我发现垃圾会滋生垃圾,我已经在软件以及床下和房间角落里看到了这种情况。

我有一种预感,进化树的主要分支会通过那些拥有最小、最干净核心的语言。你可以用语言本身编写的语言越多,就越好。

当然,我甚至在问一百年后编程语言会是什么样子时,就做了一个很大的假设。一百年后我们还会编写程序吗?我们难道不会直接告诉计算机我们想让它们做什么吗?

到目前为止,这方面还没有取得太大进展。我猜想,一百年后,人们仍然会使用我们认为的程序来告诉计算机做什么。可能有一些任务我们现在通过编写程序来解决,而在一百年后你不需要编写程序来解决,但我认为仍然会有很多我们今天所做的类型的编程。

认为任何人都可以预测任何技术在一百年后的样子,这似乎是自以为是的。但请记住,我们已经有近五十年的历史了。当我们考虑到过去五十年语言的进化速度有多慢时,展望一百年是一个可以理解的想法。

语言进化缓慢,因为它们实际上不是技术。语言是符号。程序是你希望计算机为你解决的问题的形式化描述。因此,编程语言的进化速度更像是数学符号的进化速度,而不是交通或通信的进化速度。数学符号确实会进化,但不会像你在技术中看到的那样发生巨大的飞跃。

无论一百年后计算机是由什么制成的,可以肯定地预测它们会比现在快得多。如果摩尔定律继续有效,它们将快74 quintillion(73,786,976,294,838,206,464)倍。这有点难以想象。事实上,在速度方面最有可能的预测可能是摩尔定律将停止工作。每十八个月翻一番的东西最终似乎很可能会遇到某种基本限制。但我毫不怀疑计算机会快得多。即使它们最终只快了一百万倍,这也应该大大改变编程语言的基本规则。除其他外,将会有更多的空间容纳现在被认为是慢速的语言,这意味着那些不能产生非常高效代码的语言。

然而,某些应用程序仍然需要速度。我们希望用计算机解决的一些问题是由计算机创造的;例如,你必须处理视频图像的速度取决于另一台计算机生成它们的速度。还有另一类问题,它们本身具有无限的吸收周期的能力:图像渲染、密码学、模拟。

如果某些应用程序可以越来越低效,而另一些应用程序继续需要硬件可以提供的所有速度,那么更快的计算机将意味着语言必须覆盖更广泛的效率范围。我们已经看到了这种情况正在发生。按照前几十年的标准,一些流行的新的语言的当前实现非常浪费。

这不仅仅是编程语言中发生的事情。这是一个普遍的历史趋势。随着技术的进步,每一代人都可以做前一代人会认为浪费的事情。三十年前的人们会对我们随意拨打长途电话感到惊讶。一百年前的人们会更加惊讶,一个包裹有一天会通过孟菲斯从波士顿运到纽约。

我已经可以告诉你,在未来一百年里,更快的硬件将给我们带来的所有额外周期会发生什么。它们几乎都会被浪费掉。

我在计算机能力稀缺的时候学会了编程。我记得从我的Basic程序中删除所有的空格,以便它们能够装入4K TRS-80的内存中。所有这些极其低效的软件都在燃烧周期一遍又一遍地做同样的事情,这让我想起来有点恶心。但我认为我在这里的直觉是错误的。我像一个在贫困中长大的人,即使是为了重要的事情,比如去看医生,也舍不得花钱。

有些浪费确实令人厌恶。例如,即使SUV使用永远不会耗尽且不会产生污染的燃料,它们也可能被认为是令人厌恶的。SUV令人厌恶,因为它们是解决一个令人厌恶的问题的方案。(如何让小型货车看起来更阳刚。)但并非所有的浪费都是坏的。现在我们有了支持它的基础设施,计算你的长途电话的分钟数开始显得小气了。如果你有资源,那么将所有电话都视为一种东西,无论对方在哪里,都更优雅。

有好的浪费,也有坏的浪费。我对好的浪费感兴趣——通过花费更多,我们可以获得更简单的设计的那种。我们将如何利用从新的、更快的硬件中获得的浪费周期的机会?

对速度的渴望深深地根植于我们心中,因为我们有微不足道的计算机,所以需要有意识地努力克服它。在语言设计中,我们应该有意识地寻找可以用效率换取哪怕是最微小的便利性的情况。

大多数数据结构的存在都是因为速度。例如,今天许多语言都有字符串和列表。从语义上讲,字符串或多或少是元素的字符的列表的子集。那么为什么你需要一个单独的数据类型呢?你真的不需要。字符串的存在只是为了效率。但是用hack来使程序运行得更快会使语言的语义变得混乱,这很糟糕。在语言中使用字符串似乎是过早优化的情况。

如果我们把语言的核心看作是一组公理,那么仅仅为了效率而添加不增加表达能力的额外公理,肯定是令人厌恶的。效率很重要,但我不认为这是获得效率的正确方法。

我认为解决这个问题的正确方法是将程序的含义与实现细节分开。与其同时拥有列表和字符串,不如只拥有列表,并提供某种方式给编译器优化建议,使其能够在必要时将字符串布局为连续的字节。

由于速度在程序的大部分中并不重要,因此你通常不需要费心进行这种微观管理。随着计算机变得越来越快,这将变得越来越真实。

减少关于实现的说明也应该使程序更灵活。规范在编写程序时会发生变化,这不仅是不可避免的,而且是可取的。

“Essay(随笔)”这个词来自法语动词“essayer”,意思是“尝试”。从最初的意义上讲,随笔是你为了弄清楚某件事而写的东西。这种情况也发生在软件中。我认为一些最好的程序是随笔,因为作者在开始时并不知道他们到底想写什么。

Lisp黑客已经了解了数据结构灵活性的价值。我们倾向于编写程序的第一个版本,以便它使用列表来完成所有事情。这些初始版本可能非常低效,以至于需要有意识地努力不去思考它们在做什么,就像对我来说,吃牛排需要有意识地努力不去思考它来自哪里一样。

一百年后的程序员最需要的是一种语言,你可以用尽可能少的精力拼凑出一个令人难以置信的低效的程序版本1。至少,我们会用现在的术语来描述它。他们会说他们想要一种易于编程的语言。

低效的软件并不令人厌恶。令人厌恶的是一种让程序员做不必要的工作的语言。浪费程序员的时间才是真正的低效率,而不是浪费机器时间。随着计算机变得越来越快,这一点将变得越来越清楚。

我认为摆脱字符串已经是我们可以考虑的事情了。我们在Arc中做到了这一点,而且似乎是一个胜利;一些难以用正则表达式描述的操作可以很容易地描述为递归函数。

这种数据结构的扁平化会走多远?我可以想到一些甚至让我震惊的可能性,因为我一直在有意识地拓宽我的思路。例如,我们会摆脱数组吗?毕竟,它们只是哈希表的一个子集,其中键是整数向量。我们会用列表替换哈希表本身吗?

甚至还有比这更令人震惊的前景。例如,McCarthy在1960年描述的Lisp没有数字。从逻辑上讲,你不需要单独的数字概念,因为你可以将它们表示为列表:整数n可以表示为n个元素的列表。你可以用这种方式做数学。这只是令人难以忍受的低效。

实际上没有人提议在实践中将数字实现为列表。事实上,McCarthy的1960年的论文当时根本不打算实现。这是一个理论练习,试图创建一个比图灵机更优雅的替代方案。当有人出乎意料地将这篇论文翻译成一个可用的Lisp解释器时,数字肯定没有表示为列表;它们像在其他所有语言中一样,用二进制表示。

编程语言会走得那么远,以至于摆脱数字作为一种基本数据类型吗?我提出这个问题与其说是一个严肃的问题,不如说是与未来玩一场胆小鬼游戏。这就像一个不可抗拒的力量遇到一个不可移动的物体——在这里,一个难以想象的低效实现遇到难以想象的巨大资源。我看不出为什么不。未来很长。如果我们可以做些什么来减少核心语言中的公理数量,那么这似乎是当t接近无穷大时应该押注的一方。如果这个想法在一百年后仍然让人难以忍受,那么也许在一千年后就不会了。

为了明确这一点,我并不是建议所有的数值计算实际上都将使用列表进行。我建议在任何关于实现的额外符号之前,核心语言都应该这样定义。实际上,任何想要进行大量数学运算的程序都可能会用二进制表示数字,但这将是一种优化,而不是核心语言语义的一部分。

另一种浪费周期的方法是在应用程序和硬件之间设置许多软件层。这也是我们已经看到的趋势:许多最近的语言都被编译成字节码。Bill Woods曾经告诉我,根据经验,每一层解释都会损失10倍的速度。这种额外的成本可以为你带来灵活性。

Arc的第一个版本是这种多层缓慢的一个极端例子,但也有相应的好处。它是一个经典的“元循环”解释器,写在Common Lisp之上,与McCarthy最初的Lisp论文中定义的eval函数有明显的家族相似性。整个东西只有几百行代码,所以很容易理解和更改。我们使用的Common Lisp,CLisp,本身运行在字节码解释器之上。所以这里我们有两层解释,其中一层(最上面的一层)非常低效,但该语言是可用的。我承认,勉强可用,但可用。

即使在应用程序中,将软件编写为多层也是一种强大的技术。自底向上的编程意味着将程序编写为一系列层,每一层都充当上一层的语言。这种方法往往会产生更小、更灵活的程序。这也是通往圣杯——可重用性的最佳途径。语言的定义是可重用的。你可以将应用程序的更多部分推到用于编写该类型应用程序的语言中,你的软件的可重用性就越高。

不知何故,可重用性的概念在20世纪80年代与面向对象编程联系在一起,而且似乎没有任何相反的证据能够将其摆脱。但是,尽管一些面向对象的软件是可重用的,但使其可重用的是其自底向上的特性,而不是其面向对象的特性。考虑一下库:它们是可重用的,因为它们是语言,无论它们是用面向对象风格编写的还是不是。

顺便说一句,我不预测面向对象编程的消亡。虽然我认为它对优秀的程序员没有太多好处,除了在某些专业领域,但它对大型组织来说是不可抗拒的。面向对象编程提供了一种可持续的方式来编写意大利面条式代码。它可以让你将程序作为一系列补丁来积累。大型组织总是倾向于以这种方式开发软件,我预计这在一百年后和今天一样真实。

既然我们在谈论未来,我们最好谈谈并行计算,因为这个想法似乎就存在于那里。也就是说,无论你在什么时候谈论,并行计算似乎都是未来会发生的事情。

未来会赶上它吗?人们至少在20年前就开始谈论并行计算是一种迫在眉睫的事情,但到目前为止,它并没有对编程实践产生太大的影响。或者没有吗?芯片设计师已经必须考虑它,那些试图在多CPU计算机上编写系统软件的人也必须考虑它。

真正的问题是,并行性会上升到抽象的哪个层次?在一百年后,它甚至会影响应用程序程序员吗?或者它会是编译器编写者考虑的事情,但在应用程序的源代码中通常是不可见的?

似乎有一件事是可能的,那就是大多数并行性的机会都会被浪费掉。这是我更一般的预测的一个特例,即我们获得的大部分额外计算机能力都会被浪费掉。我预计,与底层硬件的惊人速度一样,并行性将是你可以明确要求时可用的东西,但通常不会使用。这意味着我们在一百年后拥有的并行性,除了在特殊的应用程序中,不会是大规模并行性。我预计对于普通程序员来说,它更像是能够fork出最终并行运行的进程。

就像要求数据结构的特定实现一样,这将在程序的生命周期中相当晚的时候完成,当你尝试优化它时。版本1通常会忽略从并行计算中获得的任何优势,就像它们会忽略从数据的特定表示中获得的优势一样。

除了特殊类型的应用程序之外,并行性不会渗透到一百年后编写的程序中。如果这样做,那就是过早优化。

一百年后会有多少种编程语言?最近似乎出现了大量的新的编程语言。部分原因是更快的硬件允许程序员根据应用程序在速度和便利性之间做出不同的权衡。如果这是一个真正的趋势,那么我们将在一百年后拥有的硬件只会增加它。

然而,一百年后可能只有少数几种广泛使用的语言。我说这话的部分原因是乐观:似乎如果你做得非常好,你可以制作一种非常适合编写慢速版本1的语言,并且通过正确的编译器优化建议,在必要时也可以产生非常快的代码。因此,由于我乐观,我将预测,尽管它们在可接受和最大效率之间存在巨大差距,但一百年后的程序员将拥有可以跨越大部分差距的语言。

随着这种差距的扩大,分析器将变得越来越重要。现在很少有人关注分析。许多人似乎仍然认为获得快速应用程序的方法是编写生成快速代码的编译器。随着可接受性能和最大性能之间的差距扩大,越来越清楚的是,获得快速应用程序的方法是拥有一份从一个到另一个的良好指南。

当我说可能只有少数几种语言时,我不包括特定领域的“小语言”。我认为这种嵌入式语言是一个好主意,我希望它们能够扩散。但我希望它们被写成足够薄的表皮,以便用户可以看到下面的通用语言。

谁将设计未来的语言?过去十年中最令人兴奋的趋势之一是Perl、Python和Ruby等开源语言的兴起。语言设计正在被黑客接管。到目前为止,结果是混乱的,但令人鼓舞。例如,Perl中有很多令人惊叹的新颖想法。许多想法都非常糟糕,但对于雄心勃勃的努力来说,情况总是如此。按照目前的变异速度,天知道Perl在一百年后会进化成什么样子。

那些不能做的人去教书,这种说法是不正确的(我认识的一些最好的黑客是教授),但那些教书的人有很多事情做不了,这是真的。研究施加了限制性的等级限制。在任何学术领域,都有可以研究的主题和其他不能研究的主题。不幸的是,可接受主题和禁止主题之间的区别通常基于在研究论文中描述时,这项工作听起来有多么有学术性,而不是它对于获得良好结果有多么重要。最极端的情况可能是文学;研究文学的人很少会说出对那些创作文学的人有丝毫用处的话。

虽然科学领域的情况有所好转,但你可以被允许做的工作类型与产生良好语言的工作类型之间的重叠令人沮丧地小。(Olin Shivers雄辩地抱怨过这一点。)例如,类型似乎是研究论文的取之不尽的来源,尽管静态类型似乎排除了真正的宏——没有宏,在我看来,任何语言都不值得使用。

趋势不仅仅是语言被开发为开源项目而不是“研究”,而是语言由需要使用它们的应用程序程序员而不是编译器编写者来设计。这似乎是一个好趋势,我希望它能继续下去。

与一百年后的物理学不同,物理学几乎必然不可能预测,我认为原则上现在可以设计一种能够吸引一百年后用户的语言。

设计语言的一种方法是只写下你希望能够编写的程序,而不管是否有编译器可以翻译它或硬件可以运行它。当你这样做时,你可以假设无限的资源。似乎我们应该能够像在一百年后一样想象无限的资源。

人们希望编写什么样的程序?无论什么工作量最少。但又不完全是:如果你的编程思想没有受到你目前使用的语言的影响,那么什么_会是_工作量最少的。这种影响可能是如此普遍,以至于需要付出巨大的努力才能克服它。你可能会认为,对于像我们这样懒惰的生物来说,用最少的努力来表达一个程序应该是显而易见的。事实上,我们关于什么是可能的想法往往受到我们所思考的语言的限制,以至于程序更容易的公式似乎非常令人惊讶。它们是你必须发现的东西,而不是你自然而然地陷入的东西。

这里一个有用的技巧是使用程序的长度来近似编写它需要多少工作。当然,不是字符的长度,而是不同语法元素的长度——基本上是解析树的大小。最短的程序可能不一定是编写工作量最少的程序,但它足够接近,以至于你最好瞄准简洁的可靠目标,而不是附近模糊的最小工作量目标。然后,语言设计的算法变为:查看一个程序并问,有没有办法写得更短?

在实践中,用想象中的百年语言编写程序的效果会有所不同,具体取决于你离核心有多近。排序例程你现在就可以编写。但现在很难预测一百年后可能需要什么样的库。据推测,许多库将用于甚至还不存在的领域。例如,如果SETI@home有效,我们将需要用于与外星人交流的库。除非他们足够先进,以至于他们已经用XML进行交流。

在另一个极端,我认为你今天就可以设计核心语言。事实上,有些人可能会争辩说,它在1958年就已经基本设计好了。

如果百年语言今天可用,我们会想用它编程吗?回答这个问题的一种方法是回顾过去。如果现在的编程语言在1960年可用,会有人想使用它们吗?

在某些方面,答案是否定的。今天的语言假设了1960年不存在的基础设施。例如,一种缩进很重要的语言,如Python,在打印机终端上效果不佳。但是,如果把这些问题放在一边——例如,假设程序都只是写在纸上——1960年代的程序员会喜欢用我们现在使用的语言编写程序吗?

我认为会。一些想象力较差的人,他们的程序思想中内置了早期语言的产物,可能会遇到麻烦。(如何在不进行指针运算的情况下操作数据?如何在没有goto的情况下实现流程图?)但我认为最聪明的程序员如果拥有现在的语言,就不会有任何问题地充分利用它们。

如果我们现在拥有百年语言,它至少会成为一个很棒的伪代码。用它来编写软件怎么样?由于百年语言需要为某些应用程序生成快速代码,因此它可能会生成足够高效的代码,以便在我们的硬件上运行得足够好。我们可能需要比一百年后的用户提供更多的优化建议,但这仍然可能是一个净胜。

现在我们有两个想法,如果你把它们结合起来,就会提出有趣的可能性:(1)百年语言原则上今天就可以设计出来,(2)这样一种语言,如果存在,可能今天就适合编程。当你看到这些想法这样摆出来时,很难不去想,为什么现在不尝试编写百年语言呢?

当你从事语言设计时,我认为有一个这样的目标并有意识地记住它是很好的。当你学习驾驶时,他们教你的原则之一是通过瞄准远处的某个点来对齐汽车,而不是通过将引擎盖与道路上绘制的条纹对齐。即使你只关心接下来十英尺会发生什么,这也是正确的答案。我认为我们可以而且应该对编程语言做同样的事情。

注释

我相信Lisp Machine Lisp是第一种体现以下原则的语言:声明(动态变量的声明除外)仅仅是优化建议,不会改变正确程序的含义。Common Lisp似乎是第一个明确声明这一点的语言。

感谢Trevor Blackwell、Robert Morris和Dan Giffin阅读了本文的草稿,并感谢Guido van Rossum、Jeremy Hylton和Python团队的其他成员邀请我在PyCon上发言。