注册 登录  
 加关注
查看详情
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

韩国恺的博客

hanguokai.com

 
 
 

日志

 
 

Dart 中的可选类型  

2011-11-03 11:29:54|  分类: Dart |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
英文原文:Optional Types in Dart
更新日期:2012年11月14日

Dart 编程语言中最具创新的特性之一是可选类型的使用。本文件旨在解释可选类型是如何工作的。

概述
Dart 语言是动态类型的。你可以编写和运行完全没有类型标注的程序,就像使用 JavaScript 一样。

你可以选择在程序中添加类型标注:
  • 添加类型不会阻止你程序的编译和运行——即使标注不完整或错误。
  • 不论你添加了什么类型标注,你的程序都具有完全相同的语义。

然而,你可以从添加类型标注中获益。类型提供了下面这些好处:
  • 给人看的文档。明智地放置类型标注可以使别人更容易地阅读你的代码。
  • 给机器看的文档。工具可以有多种方式利用类型标注。特别是,它们可以在 IDE 中帮助提供很好的特性,如名称补全和增强的导航。
  • 早期的错误检测。Dart 提供了静态检查器,它可以警告你潜在的问题,而不用你自己查。另外,在检查模式下,Dart 自动把类型标注转换为运行时断言检查来辅助调试。
  • 有时,在编译到 JavasSript 时,类型可以帮助改进性能。

静态检查器
静态检查器(static checker)行为很像 C 中的链接。它在编译时警告你潜在的问题。这些警告中的很多是和类型相关的。静态检查器不会产生错误——不论检查器说什么,你总是可以编译和运行你的代码。

检查器不会对每个可能的类型违反都敏感。它不是类型检查器(typechecker),因为 Dart 并不是按照典型的类型系统那样使用类型。检查器会抱怨那些非常可能是真正问题的地方,而不会强迫你去满足严格的类型系统。

例如,考虑下面的代码:

class Point {
final num x, y;
Point(this.x, this.y);
Point operator +(Point other) {
return new Point(x+other.x, y+other.y);
}
String toString() {
return "x: $x, y: $y";
}
}

main() {
var p1 = new Point(0, 0);
var p2 = new Point(10, 10);

int n = p1 + p2;

print(n);
}

这里有个明显的问题。这种情况下静态检查器会产生一个警告。

Dart 中的可选类型 - hanguokai - 韩国恺的博客

注意代码依然可以运行,n 被设置为 Point 的实例,并打印出 x: 10, y: 10 的结果。

然而,不像典型的强类型系统,这样的代码:
  1. Object lookup(String key) {...} // a lookup method in a heterogenous table
    String s = lookup('Frankenstein');

  2. 检查器不会抱怨。因为这种情况下代码很有可能是对的,尽管缺少类型信息。作为程序员的通常知道程序的语义,而类型检查器(typechecker)不知道。你知道 'Frankenstein' 这个键在表中存储的是字符串,即使 lookup 方法声明返回的是 Object。

dynamic 类型
没有提供类型的时候,Dart 如何避免抱怨呢?这其中的关键就是 dynamic 类型,这是程序员没有明确给出类型时候的默认类型。使用 dynamic 类型让检查器闭嘴。

偶尔,你可能想要明确地使用 dynamic 。

Map<String, dynamic> m = {
'one': new Partridge(),
'two': new TurtleDove(),
...,
'twelve': new Drummer()};

我们本来也可以使用 Map<String, Object> 类型,但是那样的话,当我们获取内容的时候,它们将是信息很少的静态类型 Object 。因为这个 map 的值除了 Object 以外没有公共的超接口,我们可能更愿意使用 dynamic 。如果我们像这样调用 map 的值的方法:

pearTree = m['one'].container();

如果值用 Object 类型,我们会得到警告,因为 Object 不支持 container 方法。如果我们用 dynamic 类型,就不会产生警告。

范型
Dart 支持具体化范型(reified generics)。就是说,范型类型的对象在运行时携带它们的类型参数。传递类型参数给范型类型的构造函数是运行时操作。这如何与可选类型的要求相一致呢?

好吧,如果你不想总是考虑类型,范型并不强迫你。你可以创建范型类的实例,而不需要提供类型参数。例如:

new List();

这样写没问题。当然,你也可以这样写:

new List<String>();

new List();

只是这样写的快捷方式:

new List<dynamic>();


在构造函数中,类型参数起到运行时角色。实际上,它们在运行时被传递,所以在做动态类型测试的时候可以使用它们。
  1. new List<String>() is List<Object> // true: every string is an object
    new List<Object>() is List<String> // false: not all objects are strings

Dart中的范型符合程序员的直觉。下面是一些更有趣的情况:
  1. new List<String>() is List<int> // false
    new List<String>() is List // true
    new List<String>() is List<dynamic> // same as line above
    new List() is List<dynamic> // true, these are exactly the same

与此相反,类型标注(例如,变量前添加的类型或者函数和方法的返回类型)起到非运行时角色,并且不影响程序的语义。最后一个值得学习的情况:

new List() is List<String> // true as well!

你可以不用类型写程序,但是你经常会传递数据到有类型的库中。为了防止类型妨碍你,没有类型参数的范型类型被认为是任何其它范型版本的替代品(子类型)。

检查模式
在开发过程中,Dart 程序可以在检查模式(checked mode)下运行。如果你在检查模式下运行程序,在参数传递、返回结果和执行赋值时,系统将自动执行某些类型的检查。如果检查失败,程序将在该处停止执行,并带有清晰的错误信息。所以,

String s = new Object();

将会停止执行,因为 Object 不是 String 的子类型。然而,

Object foo() {
return "x";
}
String s = foo();

没问题,因为 foo 在运行时返回的实际对象就是 String,即使其类型签名说 foo 返回的是 Object 。当对象赋值给变量时,Dart 检查对象的运行时类型是否为变量(静态)声明类型的子类型。

本质上,检查模式就像是在对每次赋值、返回等情况进行子类型检查的调试器下运行。更多的一些例子:
  1. <int>[0,1, 1][2] = new Object(); // fails in checked mode

    bar(int n) {
    return n *2;
    }
    ...
    bar(3.2); // returns 6.4 in production, but fails in checked mode

在检查模式下,每次把参数传递给函数时,都要检查参数的运行时类型是否是形式参数声明的类型的子类型。我们可以很容易地纠正这个问题:
  1. bar(num n) {
    return n *2;
    }

    ...

    bar(3.2); // works fine

    int i_bar(num n) {
    return n *2;
    }

    ...

    i_bar(3.2); // fails in checked mode
    // because returned value is not an int

注意最后一行。检查发生在返回值上,即使函数的结果并没有赋值。

让我们回到之前 Frankenstein 例子上。
  1. Object lookup(String key) {...} // a lookup method in a heterogenous table
    String s = lookup('Frankenstein');

如果 lookup 方法确实返回了一个 String,那么检查模式将会顺利地执行。如果不是,那么它将捕获到这个错误。在生产模式(production mode)下,代码都可以运行,不会报错。假设 lookup 方法真的返回了一个非 String 对象,一个 Frankenstein 类的实例。那么变量 s 将容纳这个实例。Dart 绝不会强制它为一个字符串。如果 Dart 那样做就会意味着类型标注正在改变我们程序的行为,类型就不再是可选的了。

当然,如果你根本就不用类型,检查模式不会妨碍你。
  1. my_add(s1, s2) {
    return s1 + s2;
    }

    my_add(3, 4); // 7
    my_add(new Point(3, 3), new Point(4, 4)); // Point(7, 7)

所有这些检查会带来很大的性能损失,所以通常不能用在生产环境中。这些检查的好处是它们可以在源头上捕获动态类型的错误,调试问题会更容易。虽然总可以在测试过程中发现大多数这类问题,但是检查模式有利于缩小它们的范围。

使用类型
如何使用类型取决于你。如果你讨厌类型,你不必使用它们。你不会得到任何类型警告,你可以用你在其它动态语言中感到舒适的方式开发。然而你依然可以从类型中获益,因为 Dart 的类库中有类型签名,它们告诉你它们期望什么和返回什么。如果你在检查模式中运行,传递了错误的参数给类库,检查模式将在你犯错的地方发现它们。

如果你喜欢类型,你可以在任何地方使用它们,很像是静态类型语言。然而,即使那样你也不会获得同样级别的静态检查。Dart 的规则比较宽松。我们期望为这些人提供额外的工具来更加严格地解释类型标注。

我们不建议这些太极端地使用方式。应该在有意义的地方使用类型。你能做的最有价值的事情是添加类型到你类库中的公有成员的声明上。接下来,再对私有成员做同样的事。即使不需要别人维护代码,如果你离开代码几周或几个月后又回来,你会发现它是很有帮助的。在这两种情况下,你不一定要在方法体或函数体中添加类型。库的使用者从类型签名中获得价值,即使它们不是100%准确。

在函数体中,并不总是需要标注类型。有时代码足够简单,真的无所谓,类型反而可能会造成混乱。

通常,你应该设计你的代码,别让考虑类型影响你的设计。在某些情况下,有几种替代的设计,其中的某种比其它更适合使用类型。例如,你可以传递函数,而不是传递一个字符串表示要调用的函数名,这样你的代码会更有效并且更容易检查类型。Dart 同样防止以其他方式无端地使用反射(reflection)。然而,当真正有意义时,你应该毫不犹豫地使用反射。
  评论这张
 
阅读(1942)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018