返回介绍

3.3 Dart

发布于 2023-05-19 13:36:37 字数 11332 浏览 0 评论 0 收藏 0

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文