Loading...

百年语言

Original

2003 年 4 月

(本文改编自 2003 年 PyCon 的主题演讲。)

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

这值得思考,与其说是因为我们实际上会使用这些语言,不如说是因为,如果我们幸运的话,我们会使用从这一点到那一点的路径上的语言。

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

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

这只是一个猜测。我可能错了。我在这里的重点不是贬低 Java, 而是要提出进化 树的问题,让人们问,语言 X 在树上的什么位置?问这个问题的原因不仅仅是为了 让我们的鬼魂在一百年后说,我告诉过你。这是因为靠近 主干是一个有用的启发式方法,可以找到现在编程时会很不错的语言。

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

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

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

融合 对于语言来说更有可能,部分原因是 可能性空间更小,部分原因是突变 不是随机的。语言设计者有意地将 其他语言中的想法融入其中。

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

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

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

我认为不仅公理要选择好, 而且公理要少。数学家一直对 公理有这种感觉——越少越好——我认为他们 是正确的。

至少,仔细观察 语言的核心以查看是否有任何可以 剔除的公理,这必须是一项有用的练习。在我的漫长职业生涯中,我发现 垃圾会滋生垃圾,我在软件中也看到了这一点, 以及在床底下和房间的角落里。

我有一种预感 进化树的主干穿过具有最小、最干净核心的语言。 你用语言本身编写的越多, 越好。

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

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

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

语言进化缓慢,因为它们不是真正的技术。 语言是符号。程序是对 你想要计算机为你解决的问题的正式描述。因此, 编程语言的进化速度更像是 数学符号的进化速度,而不是,比如, 交通或通信。 数学符号确实在进化,但没有技术中看到的巨大 飞跃。

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

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

如果一些应用程序可以变得越来越低效,而 其他应用程序继续要求硬件所能提供的全部速度, 更快的计算机将意味着语言必须 涵盖越来越广泛的效率范围。我们已经看到 这种情况正在发生。一些 流行的新语言的当前实现令人震惊地浪费了 过去几十年的标准。

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

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

我学习编程的时候,计算机资源很匮乏。 我还记得从我的 Basic 程序中删除所有空格 以便它们可以装入 4K TRS-80 的内存中。 想到所有这些极其低效的软件 浪费周期一遍又一遍地做同样的事情,我感到 有点恶心。但我认为我在这里的直觉是错误的。我 就像一个从小就贫穷的人,无法忍受花钱 即使是为了重要的事情,比如去看医生。

某些类型的浪费确实令人厌恶。例如,SUV, 可以说即使它们使用永远不会 用完的燃料并且不会产生污染,也会很恶心。SUV 很恶心,因为它们是 一个恶心问题的解决方案。(如何让面包车看起来更 男性化。) 但并非所有浪费都是不好的。现在我们有了基础设施 来支持它,计算你的长途电话的分钟数开始显得 琐碎。如果你有 资源,将所有电话视为 一种东西,无论对方在哪里,都更优雅。

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

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

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

如果我们将语言的核心视为一组公理, 那么拥有不增加任何表达能力的额外公理,仅仅是为了效率,肯定很糟糕。效率是 重要的,但我认为这不是获得它的正确方法。

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

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

减少对实现的描述也应该使程序 更灵活。 规范在程序编写过程中会发生变化,这不仅是 不可避免的,而且是可取的。

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

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

一百年后的程序员最想要的是 一种语言,你可以在其中以最小的努力 拼凑出一个极其低效的版本 1 程序。至少,这是我们用现在的 术语来描述它。他们会说他们想要一种易于编程的语言。

低效的软件并不恶心。恶心的是一种语言 让程序员做无用功。浪费程序员的时间 才是真正的低效率,而不是浪费机器时间。这将 随着计算机变得越来越快而变得越来越明显。

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

这种数据结构的扁平化将进行到什么程度?我可以想到 即使对我来说也令人震惊的可能性,我的思想已经经过了有意识的扩展。例如,我们会摆脱数组吗?毕竟,它们 只是哈希表的一个子集,其中键是 整数向量。我们会用列表替换哈希表本身吗?

还有比这更令人震惊的前景。例如,麦卡锡在 1960 年描述的 Lisp, 没有数字。从逻辑上讲,你不需要拥有一个单独的概念 数字,因为你可以将它们表示为列表:整数 n 可以表示为一个包含 n 个元素的列表。你可以用这种方式进行数学运算。它只是 极其低效。

实际上没有人提出在实践中将数字表示为列表。事实上,麦卡锡的 1960 年论文当时 并没有打算实现。它是一个 理论练习, 试图创建一个比图灵机更优雅的替代方案。当有人意外地拿到了这篇论文, 并将其翻译成一个可工作的 Lisp 解释器时,数字当然 没有表示为列表;它们用二进制表示, 就像在其他所有语言中一样。

编程语言可以走得那么远,以至于摆脱了数字 作为基本数据类型吗?我问这个问题与其说是作为一个严肃的 问题,不如说是作为一种与未来玩鸡游戏的方式。这就像 一个不可抗拒的力量遇到一个不可移动物体的假设案例——在这里,一个难以置信的低效 实现遇到了难以置信的巨大资源。我不明白为什么不行。未来很长。如果 我们能做些什么来减少核心语言中公理的数量,这似乎是随着 t 趋于 无穷大而应该押注的一方。如果这个想法在一百年后仍然令人难以忍受, 那么在一千年后可能就不会了。

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

另一种浪费周期的做法是在 应用程序和硬件之间设置多层 软件。这也是 我们已经看到正在发生的趋势:许多最近的语言 被编译成字节码。比尔·伍兹曾经告诉我, 作为经验法则,每一层解释都会损失一个 速度因子 10。这额外的成本为你带来了灵活性。

Arc 的第一个版本是这种类型的 多级缓慢的极端情况,具有相应的优势。它 是一个经典的“元循环”解释器,编写 在 Common Lisp 之上,与 麦卡锡在最初的 Lisp 论文中定义的 eval 函数有着明显的家族相似之处。 整个东西只有几百行 代码,所以它很容易理解和修改。我们使用的 Common Lisp,CLisp,本身运行在 字节码解释器之上。所以这里我们有两层 解释,其中一层(最上面一层)极其低效, 而且语言是可用的。我承认,勉强可用,但 可用。

将软件编写为多层是一种强大的技术, 即使在应用程序内部也是如此。自下而上的编程意味着编写 一个程序作为一系列层,每一层都充当 上面一层的一种语言。这种方法往往会产生 更小、更灵活的程序。这也是通往 那个圣杯,可重用性的最佳途径。语言本身就是 可重用的。你将 应用程序的更多部分推入编写 该类型应用程序的语言中,你的软件将 更具可重用性。

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

顺便说一下,我不预测面向对象编程的消亡。虽然我认为它对优秀的程序员来说没有太多价值, 除了在某些特定领域之外,它对 大型组织来说是不可抗拒的。面向对象编程 提供了一种可持续的方式来编写意大利面条代码。它允许你将 程序作为一系列补丁累积起来。

大型组织 总是倾向于以这种方式开发软件,我认为这 在一百年后与今天一样真实。

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

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

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

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

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

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

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

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

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

当我 说可能只有几种语言时,我没有包括 特定领域的“小语言”。我认为这种嵌入式语言 是一个好主意,我预计它们会激增。但我预计 它们会被编写得足够薄,以至于用户可以看到 底层的通用语言。

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

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

尽管科学领域的情况有所改善, 但你被允许做的工作类型和 产生良好语言的工作类型之间的重叠令人沮丧地小。 (奥林·希弗斯对此 抱怨不休。)例如,类型似乎是 研究论文取之不尽的来源,尽管静态类型 似乎排除了真正的宏——在我看来,没有 宏的语言不值得使用。

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

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

设计语言的一种方法是简单地写下 你想要能够编写的程序,无论是否有 可以翻译它的编译器或可以运行它的硬件。 当你这样做的时候,你可以假设无限的资源。似乎 我们应该能够像 一百年后一样想象今天无限的资源。

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

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

在实践中,用想象中的一百年 语言编写程序将根据 你离核心有多近而产生不同的效果。排序例程你可以 现在编写。但它会 很难预测现在一百年后可能需要哪些类型的库。可以肯定的是,许多库将用于 甚至还不存在的领域。例如,如果 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 以及 PyCon 的其他 Python 团队成员邀请我演讲。