3.3 Dart
2011 年 10 月在丹麦奥胡斯市召开的 GOTO 大会 2011 上,Google 公司发布了一种新的编程语言 Dart。
GOTO 大会每年都在奥胡斯市召开,这个活动曾经叫做 JAOO(Java and Object Oriented,Java 与面向对象),在欧洲算是首屈一指的技术大会。《代码重构》的作者马丁·福勒(Martin Fowler)、维基创始人沃德·坎宁安(Ward Cunningham)1、“编程达人”大卫·托马斯(Dave Thomas)、C++ 创始人比雅尼·斯特劳斯特鲁普(Bjarne Stronstrup)2等著名的技术先驱都曾经作为演讲者在该大会上发表过演讲。
1 沃德 • 坎宁安(Ward Cunningham,1949— )是一位美国计算机程序员,维基(Wiki)概念的发明者。
2 比雅尼 • 斯特劳斯特鲁普(Bjarne Stroustrup,1950— )是一位计算机科学家,C++ 的创始人,现任德克萨斯州 A&M 大学工程学院计算机科学首席教授。
我自己也有两次登台演讲的经历,其中一次是在 2001 年。那个时候 Ruby on Rails 还没有诞生,可以说主办方的眼光十分敏锐。所有的演讲者都称赞大会的讲师阵容豪华、料理好吃,堪称“最棒的大会”。
其实,David Heinemeier Hansson 3也曾作为学生工作人员参加了 2001 年那次大会。传说,他是借在会后的饭局上跟我聊天的机会,对 Ruby 产生了兴趣,从而从 PHP 转到了 Ruby,之后在美国 37signals 公司开发出了 Ruby on Rails。
3 David Heinemeier Hansson(1979— )是一位丹麦计算机程序员,Ruby on Rails 的创始人,在 Ruby 社区中常用名为 DHH。
关于 JAOO 的题外话好像有点太多了。虽说对我个人来说这个大会给我留下了很深的印象,不过这个话题还是到此为止吧。下面我们回到主题,来讲讲 Dart。
为什么要推出 Dart ?
像“Dart 语言入门”这样的题材,不如还是留给别的杂志、图书和网站来做吧,在本书中,我们的介绍重点关注的是隐藏在 Dart 背后的“为什么”。当然,Google 公司并没有官方公布过推出 Dart 的意图,我也只是从声明以及语言设计规格中推测的。不过,即便是以这些有限的信息为出发点,却也得到了很多意外的收获。
那么,Google 公司到底为什么要开发和发布一种新的编程语言呢?像 Ruby 这样由一个人开始开发的语言,仅仅拥有对技术的兴趣,以“想做做看而已”这样的理由就足够成立了。但是 Google 公司作为一家世界上具有代表性的企业,用自己公司的名义来发布一种新的编程语言,我觉得其中一定另有深意。
况且,很多人都知道,在 Google 公司中有这样一条规定,公司内部的软件开发项目,只能使用 C/C++、Java、Python 和 JavaScript 这几种语言。之所以有这条规定,是因为所使用的语言种类越多,就需要雇佣越多精通这些语言的技术人员,而限制开发语言的种类,主要是从降低管理成本上来考虑的。软件开发是 Google 公司的生命线,先不站在技术人员兴趣的角度上来考虑,就从维系这一生命线需要管理大量的代码这个角度来看,毋庸置疑这是在公司经营层面上一个非常合理的判断。因此,虽然编程语言的开发在技术上非常吸引人,但 Google 也决不会草率地在自己公司里开发一种编程语言并发布出来。Google 不惜违背自己的规定而开发一种自己的编程语言,这背后到底有怎样的原因呢?
2009 年 Go 发布的时候,官方对于动机的解释是为了克服 C/C++ 的缺点。也就是说,Google 公司要开发的软件数量实在是太庞大了,像 C 语言这样设计古老的语言(诞生于 1972 年)便遇到了瓶颈,而 C++ 的设计由于考虑了和 C 语言之间的兼容性,因此也显得有些力不从心了。
因此,Google 公司的开发人员希望能够提供一种:
· 更现代
· 更安全
· 十分高速
的替代语言。的确,Go 在语言设计上保持了和 C 语言同等程度的高速性,同时还加入了简洁的面向对象功能、垃圾回收机制以及类型安全等特性。
进一步推测的话,像 Google 这样需要编写大量代码的公司,即便没有什么外部用户,光公司内部应该也可以保证足够多的使用者。虽然 Go 也曾在一段时间内并没有引起太大关注,但对 Google 公司来说应该也算不上是什么问题吧。
应该说,Dart 背后应该也隐藏着类似的动机,当然在这里需要被替换的语言成了 JavaScript。JavaScript 是美国原 Netscape Communications 公司作为其浏览器产品的内部语言而开发的,开发周期非常短。
JavaScript 的设计者布兰登·艾克(Brendan Eich)4曾在一次采访中说,JavaScript“几天就设计出来了”。从这样的出身来看,出乎意料,它还真算是一种做得不错的语言。但由于开发周期短,确实也存在着诸多不足。例如,开发周期短导致了语言规格和实现过于追求简单化,而程序员实际开发出的 JavaScript 代码则容易变得十分繁杂。
4 布兰登 • 艾克(Brendan Eich,1961— )是一位计算机程序员,JavaScript 语言的创始人,现任 Mozilla 公司首席技术官(CTO)。
在 Dart 发布前夕,曾经从 Google 公司内部泄露出了一份备忘录,内容如下5:
5 Google 公司对于这份备忘录的真伪没有做出官方表态。(原书注)
· JavaScript 包含一些语言本质上的缺陷,这些缺陷无法通过对语言的改进来解决。因此,我们将对 JavaScript 的未来执行两个方面的战略。
· Harmony(低风险、低回报):与 ECMAScript 标准规范小组 TC39 合作,继续努力对 JavaScript 进行改进。
· Dash 6(高风险、高回报):在保持 JavaScript 动态性质的同时,开发一种更容易提升性能的、更容易开发适合大规模项目所需工具的新语言——Dash。
6 在这份备忘录中,Dart 被称为 Dash,应该是项目途中更改了名称。(原书注)
关于执行这两方面战略的理由,这份备忘录给出了如下解释。首先,如果拘泥于 JavaScript,那么 Web 的发展就会发生停滞,从而就可能在与苹果公司的 iOS 等对手的竞争中失利。但反过来说,如果放弃 JavaScript 而只专注于 Dart,一旦 Dart 失败,则 JavaScript 的发展就会停滞,最坏的情况下甚至会危及 Google 公司在技术界的地位。因此,Google 才做出了这种“两手抓、两手都要硬”的决定。
对于这份备忘录,JavaScript 阵营,尤其是该语言的创始人布兰登·艾克回应说,性能和工具支持都不是什么大问题,即便是现在的 JavaScript 有一些缺陷,也是可以进行改善的。而且 JavaScript 的下一个版本 Harmony 中,已经对这些问题进行了一定程度的应对。
此外,JavaScript 目前受到的主要批判,如:
· 无法应对复杂的互联网应用程序
· 无法进行高速化
· 不支持多核 /GPU
· 无法修正
但这里面存在着一些误解,因此他们主张,今后还是应该专注于 JavaScript。而且,开发一种新的语言,可能会造成社区的分裂。
技术的正确与否,只能留给将来的历史去证明,我们在这里不去判断双方孰优孰劣,但至少我认为,他们双方的主张都具备各自的合理性。
下面,我们就来具体看一看 Dart 这个语言吧。
Dart 的设计目标
在 Dart 的主页 dartlang.org 7中,关于 Dart 的设计目标是这样说明的:
7 将“语言名称 +lang”作为官方网站域名,最早应该是我为 Ruby 创建的“ruby-lang.org”。虽然没有证据证明 Google 是受了我的影响,但想象一下我的想法居然能够影响到 Google,就感觉到一种莫名的开心。(原书注)
· 创造一种结构化而又十分灵活的 Web 开发语言。
· 要让 Dart 对程序员更加自然和友好,作为结果,将 Dart 设计成一种容易学习的语言。
· 构成 Dart 的全部语言机制都不应该对高速运行和快速启动产生妨碍。
· 要将 Dart 设计成一种能够适应一切 Web 相关设备的语言,包括手机、平板电脑、笔记本电脑、服务器等。
· 要在主流的现代浏览器上提供可以高速运行 Dart 的工具。
需要实现上述目标的 Web 开发者,所遇到的问题有下面这些:
· 小型脚本通常在没有实现结构化的情况下就成为了一个大型的 Web 应用程序,这样的应用程序很难进行调试和维护。而且,这种一整块的应用程序,无法由多个团队分担开发工作。Web 应用一旦变得巨大,就无法保证其开发效率。
· 能够快速编写代码的轻量化特性,是脚本语言受欢迎的原因。这样的语言中,一般来说,对应用程序模块间访问的约定(契约),并不是由语言本身来完成的,而是通过注释来表现的。结果,除了作者以外,要读懂代码并进行维护就变得非常困难。
· 现存的语言中,都要求开发者必须从静态类型和动态类型两者中选择一种。传统的静态类型语言都需要庞大的开发工具,编码风格上的制约也比较多,让人感觉缺少灵活性。
· 开发者无法在服务器和客户端上构建一个具备统一感的系统。node.js 和 Google Web Toolkit(GWT)是为数不多的例外。
· 多种语言和格式的混合,会导致繁杂的“上下文切换”问题,增加了开发的复杂性。
原来如此。作为动态语言的信奉者,我无法完全同意这些观点,不过我想就算我不说,大家也应该能理解的吧。那么,既然意识到这些问题的存在,为了实现所设定的目标,Google 公司又将 Dart 设计成了怎样一种语言呢?
代码示例
首先,我们来看一段 dartlang.org 上面的示例程序(图 1)。这个,嗯,怎么说呢,感觉和 Java 很像啊。
interface Shape { num perimeter(); } class Rectangle implements Shape { final num height, width; Rectangle(num this.height, num this.width); num perimeter() => 2*height + 2*width; } class Square extends Rectangle { Square(num size) : super(size, size); }图 1 Dart 示例程序(1)
不过,仔细看看就会发现还是有很多不同的。例如数值类型叫做 num,还有构造方法的编写十分简洁,方法定义有其他形式等。此外,super 的用法和 Java 也有些区别,不指定方法名这样的形式又有点像 Ruby。
我们再来看另外一段示例程序(图 2)。这次好像风格变得有点不一样了。
main() { var name = 'World'; print('Hello, ${name}!'); }图 2 Dart 示例程序(2)
怎么样?是不是感觉和 Java 有点相似,但又比 Java 要简洁?说起和 Java 语法相似的脚本语言,让我想起了 Groovy 8。Dart 作为 JavaScript 的后继者,试图对简洁的编程提供支持,大家是否从中感觉到了呢?用“$”将表达式嵌入到字符串中,这一点倒是很有脚本语言的风格。Ruby 也差不多,只不过用的是“#”而不是“$”。哦对了,Groovy 也是用“$”在字符串中嵌入表达式的。
8 Groovy 是一种 Java 平台上的面向对象编程语言,发布于 2003 年。它是一种动态语言,可以作为 Java 平台上的脚本语言来使用。
Dart 中无需显式指定类型,程序以 main 方法作为起点。Dart 最大的特征就在于其类型声明是可以省略的。关于这种“非强制性静态类型”的机制,我们稍后会详细进行探讨。
下面我们来创建一个类(图 3)。这次又很像 Groovy 的风格呢。这个程序很简单,就是创建一个类,并调用它的方法,好像没有什么讲解的必要呢。
class Greeter { var prefix = 'Hello,'; greet(name) { print('$prefix $name'); } } main() { var greeter = new Greeter(); greeter.greet("Class!"); }图 3 Dart 示例程序(3)
Dart 中可以创建多个构造方法。那么,我们来定义一个指定问候词的构造方法吧(图 4)。深受 Ruby 毒害的人肯定会说,这种功能用类方法来实现不就好了嘛。
class Greeter { var prefix = 'Hello,'; Greeter(); Greeter.withPrefix(this.prefix); greet(name) { print('$prefix $name'); } } main() { var greeter = new Greeter.withPrefix('Howdy,'); greeter.greet("Class!"); }图 4 Dart 示例程序(4)
Dart 的实例变量默认是公有(public)的,可以从外部进行访问。因此:
greeter.prefix = "Goodbye"就可以改写实例变量了。如果不希望公开实例变量的话,就需要将实例变量声明为私有(private)。此外,为了对属性访问进行抽象化,还可以定义 setter 和 getter 方法。如果将图 3 的程序修改一下,将 prefix 私有化并用 setter 和 getter 进行封装,就变成了图 5 的样子。
class Greeter { String _prefix = 'Hello,'; // Hidden instance variable. String get prefix() => _prefix; // Getter for prefix. void set prefix(String value) { // Setter for prefix. if (value == null) value = ""; if (value.length > 20) throw 'Prefix too long!'; _prefix = value; } greet(name) { print('$prefix $name'); } } main() { var greeter = new Greeter(); greeter.prefix = 'Howdy,'; // Set prefix. greeter.greet('setter!'); }图 5 Dart 示例程序(5)
首先,名字以“_”开头的变量是私有的。私有的实例变量无法从外部进行访问。setter 和 getter 是在方法名前面分别加上 set 和 get。在 Dart 中,setter/getter 和一般的方法是有明确区分的,因此无法定义和 setter/getter 重名的方法,此外,也无法在子类中重写这一类方法。
最后我们来简单说明一下泛型。拥有静态类型的语言,必然需要带参数的类型。因此,Dart 也理所当然地具有泛型。
在静态类型语言中,通过是否拥有带参数的类型,就能看出在语言设计的时候对于类型进行了何种程度的考量。这让人想起,早期的 Java 和 C++ 都没有泛型和模板类呢。话说回来,考虑到那些语言出现的时间,一定程度上说,这也许是没办法的事吧。
在这里我想为 Java 平反一下。Java 其实在早期就探讨过引入带参数类型,但考虑到当时带参数类型还处于研究水平,恐怕很难在设计规格上达成一致。因此,在早期的规格中就放弃了这个功能。
那么,作为现代的静态类型语言,Dart 也采用了泛型。我们在前面的示例中,尝试用了一下泛型,虽然有些牵强。在图 6 中,我们使用 List<Greeter> 来存放多个 Greeter 类。当然,由于在 Dart 中类型声明是非强制的,因此在这里用 var 程序也一样可以正常运行。
class Greeter { var name; Greeter(this.name); greet() { print('Hello ${name}.'); } } main() { List<Greeter> greeters = [new Greeter("you"),new Greeter("me")]; for (var g in greeters) g.greet(); }图 6 Dart 的示例程序(6)
Dart 的特征
刚才我们对 Dart 进行了快速的了解。Dart(目前)并不是一种规格规模很大的语言,但以这点篇幅也不可能涵盖其全部特性,不过至少大家能对它的基本风格有所了解了吧。
因此,Dart 的特征,尤其是和 JavaScript 进行比较的话,我认为比较重要的应该是:
· 基于类的对象系统
· 非强制性静态类型
当然,除此之外还有其他一些细微的差异,但如果说 Dart 和 JavaScript 之间决定性的差异的话,我想非上述两点莫属了。
基于类的对象系统
在 JavaScript 中,对象的实现基本上是用散列表(hash table)的方式。JavaScript 中,除了数值和字符串,几乎所有的数据都是对象(散列表)或者函数(函数对象),也就是说,基本上是用这两种数据结构来“以不变应万变”。
散列表的数据取出访问数量级为 O(1) 9,无论表的大小如何,都能以一定的速度来取出数据,是一种很优秀的数据结构。但遗憾的是,与直接访问数组和结构体相比,无论是数据的取出还是更新,所需的时间都要长得多。
9 在算法中,O(N)(大 O 记法)表示在最坏的情况下,一种算法的性能与其对象数据量之间的关系。O(1) 表示算法性能与数据量无关,即数据量的大小不影响该算法的性能。更多关于大 O 记法的解释请参见 4.1 节中的相关内容。
在以 Google Chrome 内置的 v8 为代表的现代 JavaScript 引擎中,作为优化的一部分,在满足一定条件的情况下,会将对象以结构体的方式来实现。然而,Dart 天生就具备基于类的对象系统,因此不需要进行这种不自然的优化行为。作为结果,以简单的引擎来实现高性能,这一点是非常值得期待的。
话说,JavaScript 在下一个版本 Harmony 中也采用了基于类的对象系统。虽说这样一来,JavaScript 方面会面临着和原有版本的兼容性问题,但可以想象,今后 Dart 的优势将逐渐被削弱。
非强制性静态类型
Dart 最大的特征莫过于非强制性静态类型了。由于类型的描述和程序本身的逻辑没有直接关系,因此有很多人觉得类型是十分繁琐的,但类型也并非一无是处。首先,虽然类型信息与程序逻辑没有直接关系,但属于重要的附属信息。通过类型的矛盾,经常可以检查出程序的错误。虽说程序中的类型信息没有矛盾,这并不代表程序就没有错误,但至少有相当多的错误,是可以通过类型信息由机器检查出来的。
此外,通过在程序中附加类型信息,使得在编译时可以用来进行优化的信息增加,就更有可能生成出高品质和高性能的代码。进一步说,IDE 等工具的自动完成等辅助功能,也可以帮助更好地利用类型信息。
静态类型有如此多的好处,但另一方面,小规模的程序中如果强制对类型信息进行描述的话,类型信息所占的比例就会相当大,从而使得程序逻辑的本质被埋没,也会消磨开发的欲望。
为了解决这个矛盾,某些语言采用了类型推导(type inference)机制,而 Dart 则是采用了“非强制性(可省略)静态类型”(optional typing)的方法。在 Dart 中,没有指定类型的变量和表达式会被当做 Dynamic 型,其类型检查在运行时完成。
采用非强制性系统的语言并非只有 Dart,这些语言最大的问题在于,如果类型信息是非强制性(可省略)的,在运行过程中类型信息就会逐渐减少,导致可进行类型检查的范围不断缩小。结果,在编译时可以发现错误这一静态类型所具备的优势就没了一半。此外,随着类型信息的减少,能够用于优化的信息也同时减少,从这一点上来说也有点得不偿失。
基于这些问题,Dart 进行了大胆的突破。也就是说,Dart 是类似 JavaScript 这样,在语言本质层面不具备类型信息的动态语言,而静态类型信息仅仅是作为辅助地位而存在的。在 Dart 的语言规格中,明确记载了 Dart 具备完全不进行类型检查的工作模式。也就是说,在没有显式打开类型检查器的情况下,例如:
num n = "abc";这样的程序是完全可以正常运行的。
大概很多人会问,这样到底有什么好处呢?说实话,我也有同样的疑问。
我就大胆推测一下,如果使用了带类型信息的库,IDE 等的自动完成功能已经十分有效,而且程序中也会进行一定程度的类型检查,这是其一。另外,随着自己所开发的程序规模逐渐扩大,可以阶段性地增加静态类型信息,从而同时享受了动态类型和静态类型双方的优点。
这样一说的话好像也能说得通,但与此同时,我还是会对这种机制是否能够成功表示怀疑。
Dart 的未来
那么,在这样的背景下诞生的 Dart,今后会不会普及呢?
个人认为,Dart 的未来还真不能说有多么光明。理由有很多,首先一个就是期望与现实的差距。
一种编程语言并不是有了语言的引擎就算完成了,而是必须在这种语言得以立足的库、框架、应用程序等“生态圈”成熟起来之后,其价值才真正开始体现。而要走到这一步,需要花上很多年的时间。Dart 诞生在 Google 公司这样的名门中,天生就被赋予了很大的期望,但要想实际建立起自己的生态圈,并成为一种可用的语言,所要花费的时间并不会和其他语言有什么不同。Dart 是否能够忍受住期望和现实之间的差距,目前还是未知数。
此外,Dart 当初的目标是为了打倒 JavaScript,但它的对手拥有大量的用户、社区和应用程序,作为新手的 Dart(尽管有 Google 公司作为后盾)却仿佛赤手空拳一般。基于类的对象系统也好,非强制性静态类型也好,虽然都是不错的概念,但这些是否具备足够的独创性和魅力,来弥补前面所说的压倒性劣势呢?我只能表示怀疑。还有,在 Dart 实用化之前,JavaScript 也一定会完成进一步的进化,战斗的形势十分严峻。
话说回来,编程语言是一种“10 年也就幼儿园小孩水平”的耐久型领域,未来的事谁都无法预测,我们只能继续关注 Dart 的发展了。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论