The Case for D —硬币的另一面
作者:leonardo(leonardom)
翻译:尚波
Andrei Alexandrescu 已经写了一篇好文章:《The Case for D》。
D1是个很好的语言,我经常使用它,但是这篇文章显示出太多D2语言和它的编译器的好处,主要聚焦在它将来可以做什么上,而忽略了当前存在的一些消极面和问题。给新有D用户提供虚假期望是危险的。我认为给出现状的一个更平衡的叙述会更好,即使在将来大多数当前的D问题可以被修正。
一篇好文章必须显示当前语言中存在的困难,而不仅仅是谈论若干年后可以看到的良好实现。目前Java是非常快的语言,编译器可以帮助程序员避免许多可预见的bug,并且工具链也非常完善。但是在Java开始的时候它非常慢,实用性也很有限,只不过是一个玩具而已。
这篇邮件不是我在D语言中见到的所有缺点的列表,它仅是关于Andrei Alexandrescu的文章的注释的列表。
从该文中:
“在开发过程中,语言复杂性有所增加,这实际上成了一个好的标志,因为没有哪个实际使用中的语言会变得很小。”
D2语言比D1更复杂,即使添加到D中的每样东西都有它的正当理由,c++语言清楚地表明,太多的复杂性并不是一件好事。因此过高的复杂性并不是一个很好的指标。
“其他的实现还没有完成,著名的包括一个.NET移植和使用LLVM基础设施作后端的编译器(译者注:ldc)。”
LDC编译器(使用LLVM后端)在Linux上和Tango标准库(缺乏内建的分析器)已经可以编译D1的代码。在Windows平台,LLVM缺乏异常支持,因此还不可用。
“D可以说成是最好的高级系统编程语言。”
很难想象用D去写像Linux内核这样的东西或去写诸如嵌入式系统的代码。对于嵌入式系统那只有几千字节的内存D编译的程序显得太大,D语言对GC的依赖太多以致于不能成为写现实生活中的内核的一个好工具。
所以D语言更像一个类系统编程的语言。一个多层次的语言,可以用来写接近于“金属”(Metal,注:可能是笔误,应为Meta)的代码相当接近“金属”或者也可以写高级通用的代码。
“它包含的特性可以正常地在高级语言甚至脚本语言中找到—如快速的编译-运行周期。”
在D中由编译后的模块做成的程序,其编译-运行周期可同C#和Java一样快。
“事实上,D可以直接链接和调用c函数而不需要要中间翻译层。”
在Windows上你必须用DMC编译C代码来做到这一点。
“不过,你很少感到强迫自己去用低级语言,因为D自己的设施更强大、安全和有效率。”
目前,在实践中存在一些使用C风格代码比D1的性能更高的情况(特别是使用DMD编译器而不用LDC时)。
“内建支持文档和单元测试。”
这样的东西非常方便也非常好。但目前对文档的内建支持有很多bug,并且内建的单元测试非常原始和有限:例如测试没有名字,他们只包含了正常的代码和断言(assert()),一旦第一个断言失败运行就停止。
如:
return printf("hello, world\n") < 0;
在C中可能更正确:
if (printf("hello, world\n") >= 0) return EXIT_SUCCESS; else return EXIT_FAILURE;
(and T!(X) or simply T!X for T<X>)
在D1中不支持T!X语法。在D2中存在另一个规则,你不能把
T!(U!(X))写成T!U!X
这个例子显示d2更复杂,它要保存两个字符。
“D的编译、保护、模块化单位是文件。包的单位是一个目录。”
D的模块系统是很好很方便,但它现在有一些bug,并具有一些语义漏洞。
留给程序员的感觉是开头设计得好,但中段的开发设计已经停止,留下一些未完成的功能。例如,如果你导入模块“foo”,在当前命名空间中它导入的不只是“foo”,而是所有包含在“foo”中的名字和“foo”自身。这是愚蠢的。
循环导入语义、包语义和安全性也存在一些问题(缺乏从一个模块中导入所有名称的语法。把从一个模块中导入所有名称作为默认行为是糟糕的。)
另一个不利的是,所有现存的D编译器不能自己跟踪模块树以编译代码,因此你需要告诉编译器你要编译的所有模块,即使这样的信息已经完全呈现在代码本身里。有几种工具试图补充这个基本功能的漏洞(非常大的程序需要更复杂的策略,但经验告诉我们大部分很小的D程序可以用自动编译模型很好的处理)。
“一、语言的语法允许单独分解、解析以得到高度的优化。”
这也有坏处,它限制了在语言中有可能使用的语法,例如它使这个代码不可能 foreach (i, item in items)
强制语言要这样使用,就减少了一些可读性,增加了一些可能的bug:
“三、Walter Bright,D语言创造者和最初实现者,是一个资深的优化专家。”
这可能是真的,尽管DMD后台会产生不太高效的代码。LDC(LLVM后端)在当前通常更好。
“其他面向对象的编程语言只有小的改进,”
不真实,看看Clojure和Scala。但愿D会做得更好。
“函数式语言的兴盛是一个大趋势”
更多的人在谈论文艺复兴:-)
“SafeD集中在消除内存崩溃的可能性。”
也许更的是把其它的安全性添加到 SafeD这样的模块中。
“这使得Java和c#代码非常易于移植到D实施的工作上。”
这的确很容易地移植C/Java代码到D。但转换C头文件到D需要一些工作。当前D的垃圾收集器没有Java通用的那个有效率,因此D需要更少分配的代码。
“显式override关键字避免意外的覆盖。”
它是可选的。
“有一个技术我不能提到,因为它是有商标权的,因此让我们叫它契约式编程(contract programming)。”
它是内建在语言中的。虽然没有完整实现,但如果你没有把它用到埃菲尔铁塔( Eiffel)上的话已足够了。
“现在的实现带有O(n)time和尾部调用优化(tail call optimization),要注意空间复杂性”
目前只有LDC(一个d1编译器)可以执行尾部调用消除(tail-call elimination),并且只能在简单的情形下。不过随着LLVM的改进,LDC也会改进。
“当迭代是首选方法时,iron-clad 功能纯正的保障和舒适地实现,如果不是太酷了,我不知道它是什么。”
目前不能在循环外部调用纯函数( pure functions)。如果纯函数产生一个内存溢出异常或涉及到浮点舍入模式的改变时,就可能存在一个问题。
函数式编程巧妙地处理大量不可变数据,这样会使垃圾收集器有很大的压力。当前D的GC对于这样快速的内存分配周期效率不足,因此它不适合函数式编程或Java风格那种频繁分配的面向对象编程风格。
所有的这些不是要阻拦你使用D1/D2语言。