ESR:程序语言设计的要诣和真谛

2018-02-01 10:18:57 浏览数 (1)

为什么一些语言会成功,另一些语言会失败。

-- Eric Raymond

当你真正掌握了整体化的工程设计思维时,你就会发现高屋建瓴的工程设计已经远远超越了技术优化的层面。我们的每一件创造都催生于人类活动的大背景下,被这种人类活动赋予了广泛的经济学意义、社会学意义,甚至于具有了奥地利经济学家所称的“人类行为学意义praxeology”。而这种人类行为学意义则是明确的人类行为所能达到的最高层次。

对我来说这并不只是一种抽象的理论。当我在撰写关于开源项目开发的文章时,文章的内容正是关于人类行为学的 —— 这些文章并不涉及哪个具体的软件技术或者话题,而是在讨论科技所服务的人类行为。从人类行为学角度对科技进行更深入的理解,可以帮助我们重塑科技,并且提升我们的生产力和成就感。这种提升并不总是因为我们有了更新的工具,而更多的是因为我们改变了使用现有工具的思路,提升了我们对这些工具的驾驭能力。

在这个思路之下,我的随笔文章的第三篇中谈到了 C 语言的衰退和正在到来的巨大改变,而我们也确实能够感受到系统级别编程的新时代的到来。在这里,我会把我的统观见解总结成更具体的、更实用的对计算机语言设计的分析。例如总结出为什么一些语言会成功,另一些语言会失败。

在我最近的一篇文章中,我写道:所有计算机语言的设计都是对机器资源和程序员人力成本的相对权衡的结果;是对一种相对价值主张的体现。而这些设计思路都是在硬件算力成本不断下降,程序员人力成本相对稳定且可能不减反增的背景下产生的。我还强调了语言设计在实现了一些原有的权衡方案之后,其未来的转化和演变成本在这种语言的成败中所要扮演的一些额外角色。在文中我也阐述了编程语言设计者是如何为当前和可见的未来寻找新的最优设计方案的。

现在我要集中讲解一下我在上面段落里最后提到的那个概念,即语言设计工程师们其实可以在多个方面来改进和提高现阶段编程语言的设计水准。比如输入系统的优化,GC (垃圾回收机制) 和手动内存分配的权衡,命令导向、函数导向和面向对象导向的混合和权衡。但是站在人类行为学的角度去考量,我认为设计师们一定会做出更简单的设计权衡,即针对近景问题还是针对远景问题来优化一种语言的设计。

所谓的“远”、“近”之分,是指随着硬件成本的逐渐降低,软件复杂程度的上升和由现有语言向其他语言转化的成本的增加,根据这些因素的变化曲线所做出的判断。近景问题是编程人员眼下看到的问题,远景问题则是指可预见的,但未必会很快到来的一系列情况。针对近景问题的解决方案可以被很快部署下去,且能够在短期内非常有效,但随着情况的变化和时间的推移,这种方案可能很快就不适用了。而远景的解决方案可能因为其自身的复杂和超前性而夭折,或是因其代价过高无法被接受和采纳。

在计算机刚刚面世的时候, FORTRAN 就是一个近景设计方案, LISP 语言的设计则是针对远景问题;汇编语言多是近景设计方案,很好的阐明了这类设计很适用于非通用语言,同样的例子还包括 ROFF 标记语言。PHP 和 Javascript 则是我们后来看到的采用了近景设计思维的语言。那么后来的远景设计方案有哪些例子呢? Oberon、Ocaml、ML、XML-Docbook 都是它的例子。学术研究中设计出的语言多倾向于远景设计,因为在学术研究领域,原创性以及大胆的假设与想法是很重要的。这和学术研究的动机以及其奖励机制很有关系(值得注意的是,在学术研究中,倾向于远景设计的本源动机并非出于技术方面的原因,而是出自于人类行为,即标新立异才能在学术上有成绩)。学术研究中设计出的编程语言是注定会失败的;因为学术研究的产物常常有高昂的转入成本,无人问津的设计。这类语言也因为在社区中不够流行而不能得到主流的采纳,具有孤立性,且常常被搁置成为半成品。(如上所述的问题正是对 LISP 历史的精辟总结,而且我是以一个对 LISP 语言有深入研究,并深深喜爱它的使用者的身份来评价 LISP 的)。

一些近景设计的失败案例则更加惨不忍睹。对这些案例来说,我们能够期待的最好的结果就是这种语言可以消亡的相对体面一些,被一种新的语言设计取而代之。如果这些近景设计导向的语言没有死亡而是一直被沿用下去(通常是因为转化成本过高),那么我们则会看到不断有新的特性和功能在这些语言原来的基础之上堆积起来,以保持它们的可用性和有效性。直到这种堆积把这些语言变得越来越复杂,变的危若累卵且不可理喻。是的,我说的就是 C 。当然, 还有 Javascript。Perl 也不例外,尽管它的设计者 Larry Walls 有不错的设计品味,避免了很多问题,让这种语言得以存活了很多年。但也正是因为 Larry Walls 的好品味,让他在最终对 Perl 的固有问题忍无可忍之后发布了全新的 Perl 6。

从这种角度去思考程序语言,我们则可以把语言设计中需要侧重的目标重新归纳为两部分: (1)以时间的远近为轴,在远景设计和近景设计之间选取一个符合预期的最佳平衡点;(2)降低由一种或多种语言转化为这种新语言的转入成本,这样就可以更好地吸纳其它语言的用户群。接下来我会讲讲 C 语言是怎样占领全世界的。

在整个计算机发展史中,没有谁能比 C 语言在选择远景和近景设计的平衡点的时候做的更完美。事实胜于雄辩,作为一种实用的主流语言,C 语言有着很长的寿命,它目睹了无数个竞争者的兴衰,但它的地位仍旧不可取代。从淘汰它的第一个竞争者到现在已经过了 35 年,但看起来 C 语言的终结仍旧不会到来。

当然,你可以把 C 语言的持久存在归功于文化惰性,但那是对“文化惰性”这个词的曲解,C 语言一直得以延续的真正原因是因为目前还没有人能提供另一种足够好的语言,可以抵消取代 C 语言所需要的转化成本!

相反的,C 语言低廉的内向转化成本inward transition costs(转入成本)并未引起大家应有的重视,C 语言几乎是唯一的一个极其多样和强大的编程工具,以至于从它漫长统治时期的初期开始,它就可以适用于多种语言如 FORTRAN、Pascal、汇编语言和 LISP 的编程习惯。在一九八零年代我就注意到,我常常可以根据一个 C 语言新人的编码风格判断出他之前在使用什么语言,这也从另一方面证明了 C 语言可以轻松的被其它语言的使用者所接受,并吸引他们加入进来。

C 语言同样胜在它低廉的转化成本。很快,大部分新兴的语言为了降低自身的转入成本,都纷纷参考了 C 语言的语法。值得注意的是这给未来的语言设计带来了一种影响:即新语言的设计都在尽可能的向 C 的语法靠拢,以便这种新语言可以有很低的内向转化成本(转入成本),使其他语言的使用者可以欣然接受并使用这种新语言。

另一种降低转入成本的方法则是把一种编程语言设计的极其简单并容易入手,让那些即使是没有编程经验的人都可以轻松学会。但做到这一点并非易事。我认为只有一种语言 —— Python —— 成功的做到了这一点,即通过易用的设计来降低内向转化成本。对这种程序语言的设计思路我在这里一带而过,因为我并不认为一种系统级别的语言可以被设计的像 Python 一样傻瓜易用,当然我很希望我的这个论断是错的。

而今我们已经来到 2017 年末尾,你一定猜测我接下来会向那些 Go 语言的鼓吹者一样对 Go 大加赞赏一番,然后激怒那些对 Go 不厌其烦的人群。但其实我的观点恰恰相反,我认为 Go 本身很有可能在许多方面遭遇失败。Go 团队太过固执独断,即使几乎整个用户群体都认为 Go 需要做出某些方面的改变了,Go 团队也无动于衷,这是个大问题。目前,Go 语言的 GC 延迟问题以及用以平衡延迟而牺牲掉的吞吐量,都可能会严重制约这种语言的适用范围。

即便如此,在 Go 的设计中还是蕴含了一个让我颇为认同的远大战略目标。想要理解这个目标,我们需要回想一下如果想要取代 C 语言,要面临的短期问题是什么。正如我之前提到的,这个问题就是,随着软件工程项目和系统的不断扩张,故障率也在持续上升,这其中内存管理方面的故障尤其多,而内存管理故障一直是导致系统崩溃和安全漏洞的主要元凶。

我们现在已经认清,一种语言要想取代 C 语言,它的设计就必须遵循两个十分重要准则:(1)解决内存管理问题;(2)降低由 C 语言向本语言转化时所需的转入成本。从人类行为学的角度来纵观编程语言的历史,我们不难发现,作为 C 语言的准替代者,如果不能有效解决转入成本过高这个问题,那设计者所做的其它部分做得再好都不算数。相反的,如果一种 C 的替代语言把转入成本过高这个问题解决地很好,即使它在其他部分做的不是最好的,人们也不会对这种语言吹毛求疵。

而 Go 正是遵循了上述两点设计思路,虽然这个思路并不是一种完美无瑕的设计理论,也有其局限性。比如,目前 GC 延迟的问题就限制了 Go 的推广。但是 Go 目前选择了照搬 Unix 下 C 语言的传染战略,把其自身设计成一种易于转入,便于传播的语言。这样它的广泛和快速的传播就会使其迅速占领市场,从而打败那些针对远景设计的看起来更好的语言。

没错,我所指的这个远景设计方案就是 Rust。而 Rust 的自身定位也正是一种远景和长期的 C 语言替代方案。我曾经在之前的一些文章中解释过我为什么认为 Rust 还没有做好和 Go 展开竞争的准备。TIBOE 和 PYPL 的语言评价指数榜也很好的证明了我的对于 Rust 的这个观点。在 TIBOE 上 Rust 从来没有进过前 20 名。而在 TIBOE 和 PYPL 两个指数榜上, Rust 都要比 Go 的表现差很多。

五年后的 Rust 会发展的怎样还未可知。但如果 Rust 社区的开发人员对这种语言的设计抱着认真投入的态度,并愿意倾听,那么我建议他们要特别重视转入成本的问题。以我个人经历来说,目前由 C 语言转入 Rust 语言的壁垒很高,使人望而却步。如果 Corrode 之类的 Code-lifting 工具只是把 C 语言映射为不安全的 Rust 语言,那么 Corrode 这类工具也是不能解决这种转入壁垒的。或者如果有更简单的方法能够自动注释代码的所有权或生命周期,那么编译器就能把 C 代码直接映射到 Rust,人们也不再需要 Corrode 这一类工具了。目前我还不知道这个问题要如何解决,但我觉得 Rust 社区最好能够找到一种解决方案来代替 Corrode 和其同类工具。

在最后我想强调一下,Ken Thompson 曾经有过语言设计的辉煌历史。他设计的一些语言虽然看起来只是为了解决近景问题,实际上却具有很高的质量和开放程度,让这些语言同样非常适合远景问题,非常易于被提高和拓展。当然 Unix 也是这样的, 这让我不禁暗自揣测,那些我认为的 Go 语言中乍看上去不利于其远景发展的一些令人担忧烦扰的设计(例如缺乏泛型)也许并没有我想象的那样糟糕。如果确如我所认为的那样,即这些设计会影响 Go 的远景发展,那么恐怕我真的是比 Ken 还要聪明有远见了。但是我并不认为我有那么高明。Go 的前途我们还是只能拭目以待。

作者:Eric Raymond

[2]

译者:Valoniakim,yunfengHe校对:yunfengHe,wxy

本文由LCTT原创编译,Linux中国荣誉推出

LCTT 译者

0 人点赞