虽然 Haxe 有一个静态的类型系统,但这个类型系统实际上可以通过使用 Dynamic 类型关闭。一个 动态值可以被赋值给任何类型;而任何值都可以被分配到动态类型。这有一些缺点:
- 编译器将不会再在接受指定类型的赋值、函数调用和其它构造中进行类型检查。
- 某些优化,特别是编译为静态目标语言时,将不能再被使用。
- 一些常见的错误,例如字段访问中的一个拼写错误,将不能在编译时被发现,可能会引发运行时的错误。
- 如果字段是通过 Dynamic 类型使用,无用代码消除(第8.2节) 不能检测使用到的字段
使用 Dynamic 类型可能引发运行时错误的例子非常容易出现。思考下面的两行代码到静态目标语言的编译:
var d:Dynamic = 1;
d.foo;
尝试在 Flash 播放器运行编译后的程序,会产生一个错误 ,属性 foo
在 数值类型中没有找到,而且没有默认值。不使用 Dynamic,这会在编译时被侦测到。
花絮:Haxe 3之前的 Dynamic 类型推断 Haxe 3 编译器从不推断一个类型为 Dynamic 类型,所以用户必须明确它。之前的 Haxe 版本曾经推断 数组为一个混合类型,如
[1, true, "foo"]
为Array
。我们发现这个行为会引发太多的类型问题,因此在 Haxe 3 中移除了它。
应该尽量少的使用 Dynamic 类型,因为很多情况下都有更好的选择,但是有时候实际会用到它。Haxe 反射(第10.7节) API中部分使用了 Dynamic 类型,而且有时候它是在处理编译时未知的自定义数据结构最好选择。当被用一个 单形(第2.9节)统一(第3.5节) 的时候,Dynamic 类型以一种特殊的方式运行。单形没有绑定到 Dynamic,这可以在如下例子中带来令人惊喜的结果:
class Main {
static function main() {
var jsonData = ’[1, 2, 3]’;
var json = haxe.Json.parse(jsonData);
$type(json); // Unknown<0>
for (i in 0...json.length) {
// Array access is not allowed on
// {+ length : Int }
trace(json[0]);
}
}
}
尽管 Json.parse
的返回类型是 Dynamic 类型,局部变量 json
的类型并没有绑定到动态类型,仍然保持了一个单形。然后它在 json.length 字段访问上被推断为一个 匿名结构(第2.5节),使后面的 json[0]
数组访问失败。为了避免这个问题,变量 json
可以通过使用 var json:Dynamic
显式的声明为 Dynamic 类型。
花絮:标准库中的 Dynamic 类型 Dynamic 类型在 Haxe 3 之前非常频繁的使用在标准库中。随着 Haxe 类型系统的持续改进,Dynamic 类型的出现在通往 Haxe 3的版本被减少。
Dynamic 是一个特殊的类型,因为它允许使用和不使用一个 类型参数(第3.2节) 来进行显式的声明。如果这样一个类型参数被提供,Dynamic(第2.7节) 中描述的 语义被限制为所有的字段兼容该参数类型:
var att : Dynamic<String> = xml.attributes;
// 有效, 值为一个 String 类型
att.name = "Nicolas";
// dito (这个文档太旧了)
att.age = "26";
// error,值不是 String 类型
att.income = 0;
类可以 实现(第2.3.3节) Dynamic 类型,和提供任意字段访问的 Dynamic 。前一种情况,字段可以有任何类型,而后一种,它们被限制兼容参数类型:
class ImplementsDynamic
implements Dynamic<String> {
public var present:Int;
public function new() {}
}
class Main {
static public function main() {
var c = new ImplementsDynamic();
// 有效的,present 是一个存在的字段
c.present = 1;
// 有效,分配的值是一个 String
c.stringField = "foo";
// 错误,Int应该是 String
//c.intField = 1;
}
}
实现 Dynamic 不符合实现其它接口的需求。预期的字段仍然必须被明确实现。实现 Dynamic 的类(带或者不带类型参数)也可以利用一个特别的方法名字叫做 resolve
。如果一个 读访问(第4.2节) 被做出,而且被讨论的字段不存在,resolve
方法被调用,并以这个字段的名字作为参数:
class Resolve implements Dynamic<String> {
public var present:Int;
public function new() {}
function resolve(field:String) {
return "Tried to resolve " +field;
}
}
class Main {
static public function main() {
var c = new Resolve();
c.present = 2;
trace(c.present);
trace(c.resolveMe);
}
}