语言进化的推动力
最近又读了一些进化论。联想到当前,最有潜力和最有影响力的几种计算机语言:C/C++,Java,.Net,D,当然其中.Net不是单一的语言,而是一个多语言的平台。其中D语言大家相对比较陌生。这里我要重点说的是C++和D。
先说C/C++。
虽然C++和C有很大的差异,但我还是要把他们放在一起,因为它们的很多共同的优缺点。先说优点:
C/C++历史悠久,有广泛的支持平台、的现有库、用户基础、性能就更不用说。但是它们有缺点,很致命,就是开发效率低。虽然使用现代C++在很大程度上改善了开发效率低下的问题。但是C++相比于C还有另外的缺点:
1. 学习难度高!以至于大家经常说,搞了5年C++,不敢说熟悉C++,更不敢妄谈精通了。对于有相当功底的C++开发人员,也经常出错。
2. 编译时间长。这主要归因于对C预处理器和语法的兼容,现代C++程序,一般都使用相当大量的模板,这使得编译时间不可接受。一个简单的spirit语法分析器,仅一个源文件,在2.4G双核至强4CPU的服务器上编译,超过一分钟。更不用说使用模板无法保密源码。
3. 当前(C++0x之前)的C++,对模板元编程的语法支持非常差,boost.mpl的那一套虽然解决了问题,但是相当丑陋。
这几个缺点在D语言中基本上不存在,主要是因为D语言是新设计的,并且它完全抛弃了C++:“对C尽最大可能兼容!”的设计思想。虽然它没有对C做最大可能的兼容,但是它仍然可以有效地与C进行合作,这在链接层次上完成,功能上有点类似于C++上的extern “C”。
需要思考的是,C++发展至今,经过了很多的演化,几乎在每一次重要的演化中,它新加的特性都得到了后来设计者都无法预料的发展和应用,最突出的就是,最近几年的模板元编程发挥出的巨大威力就是当初设计者完全没有预料到的。当第一份模板递归程序证明C++模板语言是一个图灵完备语言的时候,大家只是把它当作一个纯理论上的东西,从来没有想到它后来会在实际应用中有那么巨大的作用。就如同当初爱因斯坦发现时,没有预料到后来会有原子弹一样。
这里值得一提的是,为什么会出现这样的现象?复杂性理论告诉我们,当事物的复杂程度达到一定等级的时候,它的演化就会脱离它的设计者最初预订的目标。比如在生命诞生之初,在原生营养汤里面,有机分子终于因为某种原因随机合成了第一个有复制能力的复杂分子。在之后,一切的发展都显得那么自然而然。但是,这里有一个关键,就是在“那个东西”的复杂程度达到那个阈值之前,它的发展是非常缓慢的,甚至在达到那个阈值之前它就已经消亡了,实际上大多数情况都是这样,只有少数能越过那个门槛。
对于有生命的事物,越是有活力,越是复杂,它的变异点越多,它能产生的可能性空间就越大,从而有可能最终成功地越过门槛的概率就越高,当然它也必须有很大活力。这两点也许是相互依存的。
当然,这里的“有生命”,指的不是我们通常意义上说的生命,而是它的一个延伸。
看来,C++今日的成功,我们要归功于它的复杂。而不是单纯把它的失败归因于复杂。继续举boost.mpl的例子,因为当初C++模板的设计不是为了元编程,仅仅是为了更好地支持stl,更好地支持静态分派。因此,没有元编程中大概是最重要的一个功能:variadic template,这使得mpl.vector/mpl.map/bind/function/tuple等等的实现很难处理,需要写很多个仅仅是参数数目不同的偏特化版,这是个很繁琐无聊的工作。幸好,有人发明了boost.pp,使用最原始的C预处理来完成这个工作。结果当然是好的,boost.pp虽然比boost.mpl更丑陋,但是比起一遍遍地重复相同的代码要好得多。
从这里我们可以看到,冗余的,累赘的功能怎样发挥了它的能力。对C预处理语法的兼容一向被认为是C++最大的一个累赘,有很多人提出很多不同的惯例和方法来最大化避免使用C预处理语法,比如用inline代替带参数宏,用import(比如VC扩展允许import)代替include,用编译时常量代替宏常量等等。这些惯例和方法的确在某些方面解决了问题,规避了C++的缺陷。但是,假如C++98把预处理完全从语言中剔除,那么,在进行模板元编程时,就只能一遍又一遍地敲入重复代码,或者写出完全lisp式的代码如list<int,list<float,list<string,list<long> > > >,这比起list<int,float,string,long>如何?前者完全可以浇灭掉任何人对list的热情,而实现者要完成mpl.list,就只能重复一大堆一大堆的冗余代码,而这会浇灭实现者的热情。其结果就是,mpl仍然只停留在理论阶段,任何实际的需求都不会被发掘出来。template meta programming也只停留在理论阶段,我们现在使用template也仍然停留在“不同类对象的容器”阶段。
现在说D语言,它基本上是一个“大杂烩”式的语言,几乎所有主流语言的功能,它都实现了。它放弃了对C的语法级兼容,但是它仍然不抛弃“native code”这个设计思想,原则上它的所有语法和语义只为生成本机代码而优化。就是说,它的语法不是“为虚拟机代码而优化”,当然,理论上也可以生成虚拟机代码,但很难优化。也就是说,D语言在本质上只是一个“更好的C++”,最多,只是比“目前的C++”更好。它比C++简单,但同时也失去了C++复杂性中的变异性可能,它只能在C/C++的这个进化方向上更好。如果它想要更广泛的应用,就需要有更多变异点,在基本的设计原则上,不能和C/C++的重合太多,以防止变异的可能性空间被限制。
现在说Java/.Net,它们在本质上比较相像,都是为虚拟机而优化。而一旦有了虚拟机这个中间层,很多为本机执行而优化的可能性就失去了。我们可能在高层使用更广泛的指令集,而虚拟机这个中间层的指令集是固定的,它无法识别更高层指令集的优化。比如,我们为了优化视频解码程序,在C/C++中可以使用intrinsic,或者buildin function,它们把函数调用直接映射到CPU指令,这样,不使用汇编语言就可以使用如MMX/SSE/SSE2/3DNow等增强的指令集。而使用虚拟机,怎样实现?那就只能增加虚拟机的指令集。