Haxe允许许多类型的参数化,就像类字段(第4章)和枚举构造函数(第2.4.1节)。类型参数通过闭合的尖括号囊括以逗号分隔的类型参数名来定义。一个简单的例子来自于Haxe标准库,就是 Array:
Haxe allows parametrization of a number of types, as well as class fields (4) and enum constructors (2.4.1). Type parameters are defined by enclosing comma-separated type parameter names in angle brackets <>. A simple example from the Haxe Standard Library is Array:
class Array<T> {
function push(x : T) : Int;
}
每当一个Array的实例被创建,它的类型参数 T 成为一个 单形(第2.9节)。也就是说,它可以被绑定到任何类型,但是一次只有一个。可以是显式地触发绑定,通过调用构造函数并显式地提供类型(new Array<String>()
),或者隐式地触发,通过类型推断(第3.6节),例如当我们调用 arrayInstance.push("foo")
。
Whenever an instance of Array is created, its type parameter T becomes a monomorph (2.9). That is, it can be bound to any type, but only one at a time. This binding can happen explicitly by invoking the constructor with explicit types (new Array()) or implicitly by type inference (3.6), e.g. when invoking arrayInstance.push("foo").
在一个类的定义中使用类型参数时,除非加入了约束(第3.2.1节)否则这些类型参数都没有指定特定的类型。因此编译器必须假定这些类型参数可以被分配为任意类型使用。因此,不能访问类型参数的字段或者 类型转换(第5.23)为一个类型参数类型。也不可能为一个类型参数创建新的实例,除非类型参数是泛型(第3.3节)并且被相应的约束。
Inside the definition of a class with type parameters,these type parameters are an unspecific type. Unless constraints (3.2.1) are added, the compiler has to assume that the type parameters could be used with any type. As a consequence, it is not possible to access fields of type parameters or cast (5.23) to a type parameter type. It is also not possible to create a new instance of a type parameter type, unless the type parameter is generic (3.3) and constrained accordingly.
下面的表格展示了允许声明类型参数的地方:
The following table shows where type parameters are allowed:
位置 | 触发绑定 | 注意 |
---|---|---|
Class | 实例化 | 也可以于成员字段被访问时绑定 |
Enum | 实例化 | |
Enum构造函数 | 实例化 | |
Function | 调用中 | 允许用于方法和命名的局部 lvalue 函数 |
Structure | 实例化 |
函数类型参数在函数被调用时触发绑定,这样的类型参数(如果无约束)接受任何类型。但是,每次调用只接受一种类型。比如当一个函数有多个参数时:
With function type parameters being bound upon invocation, such a type parameter (if unconstrained) accepts any type. However, only one type per invocation is accepted. This can be utilized if a function has multiple arguments:
class Main {
static public function main() {
equals(1, 1);
// runtime message: bar should be foo
equals("foo", "bar");
// compiler error: String should be Int
equals(1, "foo");
}
static function equals<T>(expected:T, actual:T) {
if (actual != expected) {
trace(’$actual should be $expected’);
}
}
}
equals
函数的 expected
参数和 actual
参数都是类型 T
。这意味着对于每个 equals
的调用,这两个参数必须是相同类型。编译器承认第一个调用(两种参数类型都是 Int
)和第二个调用(两个参数都是 String
),但是第三个调用引发了一个编译器错误。
Both of the equals
function's arguments, expected
and actual
, have type T
. This implies that for each invocation of equals
, the two arguments must be of the same type. The compiler permits the first call (both arguments being of Int
) and the second call (both arguments being of String
) but the third attempt causes a compiler error due to a type mismatch.
花絮:表达式语法中的类型参数 我们常常被问及,为什么一个使用类型参数的方法不能被以
method<String>(x)
形式调用。编译器给出的错误信息不是很有用。这里,有一个简单的解释:上面的代码会把 < 和 > 都解析为二元操作符,结果会解析为(method< Sting) > (x)
。
We often get the question of why a method with type parameters cannot be called as
method<String>(x)
. The error messages the compiler gives are not very helpful. However, there is a simple reason for that: the above code is parsed as if both<
and>
were binary operators, yielding(method < String) > (x)
.
类型参数可以通过多种类型被约束:
Type parameters can be constrained to multiple types:
typedef Measurable = {
public var length(default, null):Int;
}
class Main {
static public function main() {
trace(test([]));
trace(test(["bar", "foo"]));
// String should be Iterable<String>
// test("foo");
}
#if (haxe_ver >= 4)
static function test<T:Iterable<String> & Measurable>(a:T) {
#else
static function test<T:(Iterable<String>, Measurable)>(a:T) {
#end
if (a.length == 0)
return "empty";
return a.iterator().next();
}
}
方法 test
的类型参数 T
被约束为 Iterable<String>
和 Measurable
。后者为方便起见使用了 typedef(第3.1节)关键字定义且需要兼容有一个Int
类型 名为 length
的只读属性(第4.2节)。约束指明一个兼容的类型满足:
The
test
method contains a type parameterT
that is constrained to the typesIterable<String>
andMeasurable
. The latter is defined using a typedef (3.1) for convenience and requires compatible types to have a read-only property (4.2) namedlength
of typeInt
. The constraints then indicate that a type is compatible if:
- 与
Iterable<String>
兼容 - 并具有一个
Int
类型且名为length
的属性
- it is compatible with
Iterable<String>
and- has a
length
property of typeInt
.
我们可以看到,在第7行调用 test
并传递一个空数组,以及第8行传递 Array<String>
是没有问题的。因为数组具有 length
属性以及 iterable
方法。然而,传递一个字符串作为参数,如第9行则会导致约束失败,因为字符串不兼容 Iterable<T>
。
In the above example, we can see that invoking
test
with an empty array on line 7 and anArray<String>
on line 8 works fine. This is becauseArray
has both alength
property and aniterator
method. However, passing aString
as argument on line 9 fails the constraint check becauseString
is not compatible withIterable<T>
.