很难预测一百年后的人类生活,只有少数几件事是可以确定的。那时,汽车将具备低空飞行能力,城市规划的法规将放宽,大楼可以造到几百层,大街上一天到晚看不见太阳,女性个个都学过防身术。本文只想讨论其中的一个细节:一百年后,人们使用什么语言开发软件?
为什么这个问题值得思考?原因不是我们最终会用上这些语言,而是幸运的话,我们从现在开始就能用上这些语言。
我认为,编程语言就像生物物种一样,存在一个进化的脉络,许许多多分支最终都会成为进化的死胡同。这种现象已经发生了。 Cobol语言曾经流行一时,但是现在看来没有任何后续语言继承它的思想。它就像尼安德特人 一样,进化之路已经走到了尽头。
我预言 Java也会如此。有人写信说:“你怎么能说 Java不会成功呢?它已经成功了。”我觉得这要看你的成功标准是什么。如果标准是相关书籍的出版量,或者是相信学会 Java就能找到工作的大学生数量,那么 Java确实已经成功了。当我说 Java不会成功时,我的意思是它和 Cobol一样,进化之路已经走到了尽头。
这只是我的猜测,未必正确。这里的重点不是看衰 Java,而是提出编程语言存在一个进化的脉络,从而引导读者思考,在整个进化过程中,某一种语言的位置到底在哪里?之所以要问这个问题,不是为了一百年后让后人感叹我们曾经如此英明,而是为了找到进化的主干。它会启发我们去选择那些靠近主干的语言,这样对当前的编程最有利。
无论何时,选择进化的主干可能都是最佳方案。要是你不幸选错了,变成了一个尼安德特人,那就太糟了。你的对手克鲁马努人时不时就会来攻打你,把你的食物全部偷走。
这就是我想找出一百年后的编程语言的原因。我不愿意押错赌注。
编程语言的进化与生物学进化还是有区别的,因为不同分支的语言会发生聚合。比如, Fortran分支看来正在与 Algol 的继承者聚合。理论上,不同的生物物种也可能发生聚合,但是可能性很低,所以大概从来没有真正出现过。
编程语言之所以可能出现聚合,一个原因是它的概率空间 比较小,另一个原因是它的突变不是随机的。语言的设计者们总是有意识地借鉴其他语言的设计思想。
对于语言设计者来说,认清编程语言的进化路径特别有用,因为这样就可以照着样子设计语言了。这时,认清进化的主干就不仅有助于识别现存的优秀语言,还可以把它当作设计语言的指南。
任何一种编程语言都可以分成两大组成部分:基本运算符的集合(扮演公理的角色)以及除运算符以外的其他部分(原则上,这个部分可以用基本运算符表达出来)。
我认为,基本运算符是一种语言能否长期存在的最重要因素。其他因素都不是决定性的。这有点像买房子的时候你应该先考虑地理位置。别的地方将来出问题都有办法弥补,但是地理位置是没法变的。
慎重选择公理还不够,还必须控制它的规模。数学家总是觉得公理越少越好,我觉得他们说到了点子上。
你仔细审视一种语言的内核,考虑哪些部分可以被摒弃,这至少也是一种很有用的训练。在长期的职业生涯中,我发现冗余的代码会导致更多冗余的代码,不仅软件如此,而且像我这样性格懒散的人,我发现在床底下和房间的角落里这个命题也成立,一件垃圾会产生更多的垃圾。
我的判断是,那些内核最小、最干净的编程语言才会存在于进化的主干上。一种语言的内核设计得越小、越干净,它的生命力就越顽强。
当然,猜测一百年后人们使用什么编程语言,这本身就是一个很大的假设。也许一百年后人类已经不编程了,或者直接告诉计算机想做什么,计算机就会自动完成。
不过,到目前为止,计算机智能并没有取得太大进展。我猜测一百年后,人们还是使用与现在差不多的程序指挥计算机。可能有一些我们今天需要编程解决的问题,那时已经不需要编程了,但是我想,那时还会存在大量与今天一样的编程任务。
你可能认为只有那些自以为是的人才会去预言一百年后的技术。但是,请不要忘记,软件发展的历史已经走过了 50年。在这 50年中,编程语言的进化其实是非常缓慢的,因此展望一百年后的语言并不是虚无缥缈的想法。
编程语言进化缓慢的原因在于它们并不是真正的技术。语言只是一种书写法,而程序则是一种严格符合规则的描述,以书面形式记录计算机应该如何解决你的问题。所以,编程语言的进化速度更像数学符号的进化速度,而不像真正的技术(比如交通或通信技术)的进化速度。数学符号的进化是缓慢的渐变式变化,而不是真正技术的那种跳跃式发展。
无论一百年后的计算机是什么样子,我们基本上可以断定它们的运行速度一定会快得多。如果摩尔定律依然成立,一百年后计算机的运行速度将是现在的 74乘以 10的 18次方倍(准确地说是 73 786 976 294 838 206 464倍)。真是让人难以想象。不过实际上更现实的预测并不是速度会提高这么多,而是摩尔定律最终将不成立。不管是什么东西,如果每 18个月就增长一倍,那么最后很可能会达到极限。但那时的计算机比现在快得多大概是毫无疑问的。即使最后只是略微快了 100万倍,也将实质性地改变编程的基本规则。如果其他条件不变,现在被认为运行速度慢的语言(即运行的效率不高)将来会有更大的发展空间。
那时,依然会有对运行速度要求很高的应用程序。我们希望计算机解决的有些问题其实是计算机本身引起的。比如,计算机处理视频的速度取决于生成这些视频的另一台计算机。此外,还有一些问题本身就要求无限快的处理能力,比如图像渲染、加密 /解密、模拟运算等。
既然在现实中一些应用程序本身的效率较低,而另一些应用程序会耗尽硬件提供的所有运算能力,那么有了更快速的计算机就意味着编程语言不得不应付更多的极端情况,涵盖更大范围的效率要求。我们已经看到这种情况发生了。要是以几十年前的标准衡量,有一些使用新语言开发的热门应用程序对硬件资源的浪费非常惊人。
不仅编程语言有这种现象,这实际上是一种普遍的历史趋势。随着技术的发展,每一代人都在做上一代人觉得很浪费的事情。 30年前的人要是看到我们今天如此随意地使用长途电话,一定会感到震惊。 100年前的人要是看到一个普通的包裹竟然也能享受一天内从波士顿发件、途经孟菲斯、抵达纽约的待遇,恐怕就要更震惊了。
我已经预测了,一旦未来硬件的性能大幅提高将会发生什么事。新增加的运算能力都会被糟蹋掉。
在我学习编程的年代,计算机还是稀罕玩意。我记得当时使用的微机型号是 TRS-80,它的内存只有 4K,为了把 BASIC程序装入内存,我不得不把源码中的空格全部删除。我一想到那些极其低效率的软件,不断重复某些愚蠢的运算,把硬件的计算能力全部占用,就感到无法忍受。但是,我的这种反应是错的,我就像某个出身贫寒的穷孩子,一听到要花钱就舍不得,即使把钱用在重要场合(比如去医院看病)都觉得很难接受。
某些浪费确实令人厌恶。比如有人就很讨厌 SUV(运动型多用途车),即使它采用可再生的清洁能源也改变不了看法,因为 SUV来自一个令人厌恶的想法(如何使得小货车看上去更有男子汉气概)。但是,并非所有的浪费都是坏的。既然如今的电信基础设施已经如此发达,再掐着时间打长途电话就有点锱铢必较了。如果有足够的资源,你可以将长途电话和本地电话视为同一件事,一切会变得更轻松。
浪费可以分成好的浪费和坏的浪费。我感兴趣的是好的浪费,即用更多的钱得到更简单的设计。所以,问题就变成了如何才能充分利用新硬件更强大的性能最有利地“浪费”它们?
对速度的追求是人类内心深处根深蒂固的欲望。当你看着计算机这个小玩意,就会不由自主地希望程序运行得越快越好,真的要下一番功夫才能把这种欲望克制住。设计编程语言的时候,我们应该有意识地问自己,什么时候可以放弃一些性能,换来一点点便利性的提高。
很多数据结构存在的原因都与计算机的速度有关。比如,今天的许多语言都同时有字符串和列表。从语义上看,字符串或多或少可以理解成列表的一个子集,其中的每一个元素都是字符。那么,为什么还需要把字符串单列为一种数据类型呢?完全可以不这么做。只是为了提高效率,所以字符串才会存在。但是,这种以加快运行速度为目的、却使得编程语言的语义大大复杂的行为,很不可取。编程语言设置字符串似乎就是一个过早优化的例子。
如果我们把一种语言的内核设想为一些基本公理的集合,那么仅仅为了提高效率就往内核添加多余的公理,却没有带来表达能力的提升,这肯定是一件很糟的事。没错,效率是很重要,但是我认为修改语言设计并不是提高效率的正确方法。
正确做法应该是将语言的语义与语言的实现予以分离。在语义上不需要同时存在列表和字符串,单单列表就够了。而在实现上做好编译器优化,使它在必要时把字符串作为连续字节的形式处理。
对于大多数程序,速度不是最关键的因素,所以你通常不需要费心考虑这种硬件层面上的微观管理。随着计算机速度越来越快,这一点已经越发明显了。
语言设计时,对实现方式少作限制还会使得程序具备更大的灵活性。语言的规格发生变化不仅是无法避免的,也是合理的。通过编译器的处理,按照以前规格开发的软件就会照常运行,这就提供了灵活性。
essay(论文)这个词来自法语的动词 essayer,意思是“试试看”。从这个原始意义来说,论文就是你写一篇文章,试着搞清楚某件事。软件也是如此。我觉得一些最好的软件就像论文一样,也就是说,当作者真正开始动手写这些软件的时候,他们其实不知道最后会写出什么结果。
Lisp语言的黑客早就明白数据结构灵活性的价值。我们写程序的第一版时,往往会把所有事情都用列表的形式处理。所以,这些最初版本可能效率低下得惊人,你必须努力克制自己才能忍住不动手优化它们,这就好像吃牛排的时候必须努力克制自己才能不去想牛排是从哪里来的一样,至少对我来说是这样的。
一百年后的程序员最需要的编程语言就是可以让你毫不费力地写出程序第一版的编程语言,哪怕它的效率低下得惊人(至少按我们今天的眼光来看是如此)。他们会说,他们想要的就是很容易上手的编程语言。
效率低下的软件并不等于很烂的软件。一种让程序员做无用功的语言才真正称得上很烂。浪费程序员的时间而不是浪费机器的时间才是真正的无效率。随着计算机速度越来越快,这会变得越来越明显。
我觉得,放弃字符串类型已经是大家可以接受的想法了。 Arc语言已经这样做了,看上去效果不错。以前用正则表达式很难描述的一些操作,现在用回归函数可以表达得很简单。
这种数据结构的扁平化趋势会怎么发展?我极其努力地设想各种可能,得到的结果甚至令我自己都吓了一跳。比如,数组会不会消失?毕竟数组只是散列表的一个子集,其特点就是数组的键全部都是整数向量。进一步说,散列表本身会不会被列表取代呢?
还有比这更惊人的预言。在逻辑上其实不需要对整数设置单独的表示法,因为可以把它们也看作列表,整数 n可以用一个 n元素的列表表示。这一样能完成数学运算,只是效率低得让人无法忍受。
编程语言会发展到放弃基本数据类型之一的整数这一步吗?我这样问并不是真的要你严肃思考这个问题,更多的是希望打开你对未来的思路。我只是提出一种假想的情况:如果一股不可抗拒的力量遇到了一个不可移动的物体,会发生什么事。具体就本文而言:一种效率低得不可想象的语言遇到了性能强大得不可想象的硬件,会发生什么事。我看不出放弃整数类型有什么不妥。未来相当漫长。如果我们想要减少语言内核中基本公理的数目,不妨把眼光放得远一点,想一想如果时间变量 t趋向无限会怎么样。一百年是一个很好的参考指标,如果你觉得某个想法在一百年后仍然可能是难以令人接受,那么也许一千年后它也依然难以令人接受。
让我说清楚,我的意思不是说所有的整数运算都用列表来实现,而是说语言的内核(不涉及任何编译器的实现)可以这样定义。在现实中,任何进行数学运算的程序可能都是以二进制形式表示数字,但是这属于编译器的优化,而不属于语言内核语义的一部分。
另一种消耗硬件性能的方法就是,在应用软件与硬件之间设置很多的软件层。这也是我们已经看到的一种趋势,许多新兴的语言就被编译成字节码 。比尔•伍兹曾经对我说,根据经验判断,每增加一个解释层,软件的运行速度就会慢一个数量级。但是,多余的软件层可以让编程灵活起来。
Arc语言 最初的版本就是一个极端的例子,它的层很多,运行速度非常慢,但是确实带来了相应的好处。 Arc是一个典型的“元循环”( metacircular)解释器,在 Common Lisp的基础上开发,很像约翰•麦卡锡在他经典的 Lisp论文中定义的 eval函数。 Arc解释器一共只有几百行代码,所以很便于理解和修改。我们采用的 Common Lisp版本是 CLisp,它本身是在另一个字节码解释器的基础上开发的。所以,我们一共有两层解释器,最上面那层效率低下得惊人,但是语言本身是能用的。我承认只是勉强可用,但是确实能用。
即使是应用程序,使用多层形式开发也是一种很强大的技巧。自下而上的编程方法意味着要把软件分成好几层,每一层都可以充当它上面那一层的开发语言。这种方法往往会产生更小、更灵活的程序。它也是通往软件圣杯——可重用性( reusability)——的最佳路线。从定义上看,语言就是可以重用的。在编程语言的帮助下,你的应用程序越是采用这种多层形式开发,它的可重用性就越好。
可重用性这个概念多多少少与 20世纪 80年代兴起的面向对象编程有些关联。不管怎样寻找证据,也不可能把这两件事完全分开。某些使用面向对象编程开发出来的软件确实具有可重用性,但是这不是因为它使用了面向对象编程,而是因为它的开发方法是自下而上的。以函数库为例,它们具有可重用性,是因为它们属于语言的一部分,而不是因为它们采用面向对象或者其他编程方法。
顺便说一句,我不认为面向对象编程将来会消亡。我觉得,除了某些特定的领域,这种编程方法其实没有为优秀程序员带来很多好处,但是它对大公司有不可抗拒的吸引力。面向对象编程使得你有办法对一团乱码似的代码进行可持续性开发。通过不断地打补丁,它让你将软件一步步做大。大公司总是倾向于采用这样的方式开发软件。我预计一百年后也是如此。
既然是谈论未来,最好谈谈并行计算( parallel computation),因为看上去并行计算好像就是为未来而存在的。无论怎么想,并行计算似乎都是未来生活的一部分。
它会在未来实现吗?过去二十年,人们都在说并行计算马上就会来临。但是,到目前为止,它对编程实践并没有太大影响。这是真的吗?芯片设计师已经不得不把它考虑在内,为多 CPU计算机开发系统软件的程序员也是如此。
但是,真正的问题在于,并行计算到底能达到哪个抽象层次?一百年后它就会影响到开发应用软件的程序员吗?或者,它还只是编译器作者需要考虑的事情,在应用软件的代码中根本就无处寻觅?
一种可能是,大多数可以用到并行计算的场合,人们都会放弃使用并行计算。虽然我总的预测是未来的软件会挥霍掉大部分新增的硬件性能,但是并行计算是一个特例。我估计随着硬件性能得到惊人的提升,如果你明确地说想要并行计算,那么肯定可以得到它,但是通常情况下你不会用到它。这意味着,除了一些特殊的应用程序,一百年后的并行计算不会是那种大规模的并行计算( massive parallelism)。我预料,对于普通程序员来说,一切更像对进程进行分叉,然后让多个进程在后台并行运行。
这是编程进行到很后期才要做的事情,属于对程序的优化,类似于你想开发一种特定的数据结构来取代现有的数据结构。程序的第一个版本通常会忽略并行计算提供的各种好处,就好像编程开始时会忽略某种特定的数据结构给你带来的好处一样。
除了某些特定的应用软件,一百年后,并行计算不会很流行。如果应用软件真的大量使用并行计算,这就属于过早优化了。
一百年后会有多少种编程语言?从最近来看,出现了大量的新语言。硬件性能提高是一个原因,这就允许程序员根据使用目的在运行速度和编程便利性之间做出不同的取舍。如果这就是未来的趋势,那么一百年后强大的硬件只会使得语言数目变得更多。
但是,另一方面,一百年后的常用语言可能只有很少几种。部分原因是基于我的乐观主义,我相信在未来,如果你的作品确实很出色,你可能选择的是一种开发起来很方便的语言。使用这种语言写出来的软件第一版的运行速度很慢,只有对编译器进行优化设置后运行速度才会提升。既然我抱有这种乐观主义,那么我还要做一个预言。有些语言可以达到机器的最高效率,另一些语言的效率则慢到刚刚可以运行而已,两者之间存在巨大的差距。我预言一百年后,这段差距之间的各个点上都会有对应的编程语言存在。
因为这段差距正在变得越来越大,所以性能分析器( profiler)将变得越来越重要。目前,性能分析并没有受到重视。许多人好像仍然相信,程序运行速度提升的关键在于开发出能够生成更快速代码的编译器。代码效率与机器性能的差距正在不断加大,我们将会越来越清楚地看到,应用软件运行速度提升的关键在于有一个好的性能分析器帮助指导程序开发。
我说将来可能只有很少几种常用语言,但没有把用于特定领域的“小众语言”( little language)算进去。我觉得,这些嵌入式语言的想法很不错,一定会蓬勃发展。但是我判断这些“小众语言”会被设计成相当薄的一层,使得用户可以一眼看出在底下作为基础的通用型语言,这样就减少了学习时间,降低了使用成本。
谁来设计这些未来的语言?过去 10年最激动人心的趋势之一就是开源语言的崛起,比如 Perl、 Python和 Ruby。语言设计已经被黑客接管。到目前为止这样到底是好是坏还看不清楚,但是发展势头令人鼓舞。比如, Perl就有一些绝妙的创新。不过,它也包含了一些很糟糕的想法。对于一种充满进取心、大胆探索的语言来说,这也是很正常的事。以它现在这种变化的速率,大概只有上帝才知道一百年后 Perl会变成什么样。
有一句俗话说,如果你自己做不到,那就去当老师。这在语言设计领域不成立,我认识的一些最出色的黑客就在当教授。但是,当老师的人确实有很多事情不能做。研究性职位给黑客带来了一些限制。在任何学术领域,都有一些题目是可以做的,另一些题目是不可以做的。不幸的是,这两类题目的区别通常取决于它们写成论文后看上去是不是很高深,而不是取决于它们对软件业的发展是否重要。最极端的例子可能就是文学,文学研究者的任何成果几乎对文学创作者都毫无影响。
虽然科学领域的状况要稍好一点,但是研究者可以做的题目与能够对设计优秀语言有所帮助的题目之间的交集小得令人沮丧。(奥林• 希弗斯曾经对这一点表达不满,而且说得头头是道。)比如,研究变量类型的论文好像多得无穷无尽,尽管事实上静态类型语言看来无法真正支持宏(在我看来,一种语言不支持宏,那就不值得使用了)。
新语言更多地以开源项目的形式出现,而不是以研究性项目的形式出现。这是语言的一种发展趋势。另一种发展趋势是,新语言的设计者更多的是本身就需要使用它们的应用软件作者,而不是编译器作者。这似乎是好的趋势,我期待它继续保持下去。
一百年后的物理学基本上不可能预测。但是计算机语言不一样,现在就动手设计一种一百年后可以吸引使用者的新语言,这在理论上似乎是可能的。
设计新语言的方法之一就是直接写下你想写的程序,不管编译器是否存在,也不管有没有支持它的硬件。这就是假设存在无限的资源供你支配。不管是今天还是一百年后,这样的假设好像都是有道理的。
你应该写什么程序?随便什么,只要能让你最省力地写出来就行。但是要注意,这必须是在你的思维没有被当前使用的编程语言影响的情况下。这种影响无处不在,必须很努力才能克服。你也许觉得,对于人类这样懒惰的生物,喜欢用最省力的方式写程序是再自然不过的事情。但是事实上,我们的思想可能往往会受限于某种现存的语言,只采用在这种语言看来更简单的形式,它对我们思想的束缚作用会大得令人震惊。新语言必须靠你自己去发现,不能依靠那些让你自然而然就沉下去的思维定势。
采用程序的长度作为它耗费工作量的近似指标是个很有用的技巧。这里的程序长度当然不是指字符的数量,而是指各种句法元素的总长度,基本上就是整个解析树的大小。也许不能说最短的程序就是写起来最省力的程序,但是当你一心想把程序写得简洁而不是松松垮垮时,你就更接近省力这个目标,你的日子也会变得好过得多。所以,设计语言的正确做法就变成了,看着一段程序,然后问自己是不是能把它写得更短一点?
实际上,用想象出来的一种一百年后的语言来写程序,这件事情的可靠程度,取决于你对语言内核的估计是否足够正确。常规的排序,你现在就可以写出来。但是,想要预测一百年后的语言使用什么函数库就很难了。很可能许多函数库针对的领域现在还根本不存在。比如,如果 SETI@home 计划成功,我们就需要与外星人联系的函数库了。当然,如果外星人的文明高度发达,已经到了用 XML格式交换信息的地步,那就不需要新的函数库了。
另一个极端是,我觉得今天你就能设计出一百年后的语言内核。事实上,在有些人看来,大部分语言内核在 1958年就已经设计出来了。
如果今天就能使用一百年后的编程语言,我们会用它编程吗?观古而知今。如果 1960年就能使用今天的编程语言,那时的人们会用它们吗?
在某些方面,回答是否定的。今天的编程语言依赖的硬件在 1960年并不存在。比如, Python这样的语言,正确的缩进( indentation)在编写时很重要,但是 1960年的计算机没有显示器,只有打印机终端,所以编写起来就不会很顺利。但是,如果把这些因素排除在外(你可以假设,我们只在纸上编程), 20世纪 60年代的程序员会喜欢用现在的语言编程吗?
我想他们会的。某些缺乏想象力、深受早期编程语言思想影响的人可能会觉得不可能。(没有指针运算,如何复制数据?没有 goto语句,如何实现流程图?)但是我想,那时最聪明的程序员一定能轻松地使用今天的大多数语言,假定他们能得到的话。
如果我们现在就能拥有一百年后的编程语言,那就至少能用来写出优秀的伪码 。我们会用它开发软件吗?因为一百年后的编程语言需要为某些应用程序生成快速代码,所以很可能它生成的代码能够在我们的硬件上运行,速度也还可以接受。相比一百年后的用户,我们也许不得不对这种语言做更多的优化,但是总的来看,它应该仍然会为我们带来净收益。
现在,我们的两个观点就是:( 1)一百年后的编程语言在理论上今天就能设计出来;( 2)如果今天真能设计出这样一种语言,很可能现在就适合编程,并且能够产生更好的结果。如果我们把这两个观点联系起来,那就得出了一些有趣的可能性。为什么不现在就动手尝试写出一百年后的编程语言呢?
当你设计语言的时候,心里牢牢记住这个目标是有好处的。学习开车的时候,一个需要记住的原则就是要把车开直,不是通过将车身对齐画在地上的分隔线,而是通过瞄准远处的某个点。即使你的目标只在几米开外,这样做也是正确的。我认为,设计编程语言时,我们也应该这样做。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/turingbooks/archive/2011/03/29/6285205.aspx