虽然许多地方可以产生变异,但它通常在伴随类型参数出现时使人感到意外。另外变异错误也非常容易被触发:
While variance is also relevant in other places, it occurs particularly often with type parameters and comes as a surprise in this context. Additionally, it is very easy to trigger variance errors:
class Base {
public function new() { }
}
class Child extends Base { }
class Main {
public static function main () {
var children = [new Child()];
// Array<Child> should be Array<Base>
// Type parameters are invariant
// Child should be Base
var bases:Array<Base> = children;
}
}
显然,一个 Array<Child>
不能被赋值到一个 Array<Base>
上,尽管 Child
可以被赋值到Base
。原因可能有些出乎意料:由于 array 可以被写入,比如通过它们的 push()
方法添加新的元素,因此不允许此类赋值操作。如果忽略变异错误非常容易产生问题:
Apparently,an
Array
cannot be assigned to anArray
,even thoughChild
can be assigned toBase
. The reason for this might be somewhat unexpected: It is not allowed because arrays can be written to, e.g. via theirpush()
method. It is easy to generate problems by ignoring variance errors:
class Base {
public function new() { }
}
class Child extends Base { }
class OtherChild extends Base { }
class Main {
public static function main () {
var children = [new Child()];
// subvert type checker
var bases:Array<Base> = cast children;
bases.push(new OtherChild());
for(child in children) {
trace(child);
}
}
}
这里我们使用 cast(第5.23节)推翻了类型检查,因此允许了注释行后的赋值操作。此时我们通过 base
持有了一个指向原数组的引用,且将其类型化为 Array<Base>
。这样做我们就可以为其推入(push)一个可与 Base
类型兼容的类型,比如此例中的 OtherChild
。然而,由于引用指向的原始数组是 children
,其类型为 Array<Child>
,如果我们对原始数组的元素进行遍历,会在碰上 OtherChild
实例的时候出问题。
Here we subvert the type checker by using a cast (5.23), thus allowing the assignment after the commented line. With that we hold a reference
bases
to the original array, typed asArray
. This allows pushing another type compatible withBase
(OtherChild) onto that array. However,our original referencechildren
is still of typeArray
and things go bad when we encounter theOtherChild
instance in one of its elements while iterating.
如果 Array
没有 push()
方法,且没有其它修改的手段,赋值就是安全的,因为不会有不兼容的类型被添加进数组中。在 Haxe 中,我们可以通过结构化子类型(第3.5.2节)来对类型进行相应的约束。
If
Array
had nopush()
method and no other means of modification, the assignment would be safe as no incompatible type could be added to it. This can be achieved by restricting the type accordingly using structural subtyping:
class Base {
public function new() { }
}
class Child extends Base { }
typedef MyArray<T> = {
public function pop():T;
}
class Main {
public static function main () {
var a = [new Child()];
var b:MyArray<Base> = a;
}
}
这样我们就可以安全地将其赋值给 MyArray<Base>
类型的变量 b
,因为 MyArray
只有一个 pop()
方法。MyArray
中也没有定义可用于添加不兼容类型的方法。此时称其发生了协变。
We can safely assign with
b
being typed asMyArray<Base>
andMyArray
only having apop()
method. There is no method defined onMyArray
which could be used to add incompatible types. It is thus said to be covariant.
协变
定义 协变:
如果一个复合类型的组分的类型可以被赋值到一个不那么明确(less specific)的类型的组分上,该复合类型便被认为发生了协变,也即是说组分只被读取,不被写入。
[warning] Definition Covariance:
A compound type is considered covariant if its component types can be assigned to less specific components, i.e. if they are only read, but never written.
定义 逆变: 如果一个复合类型的组分的类型可以被赋值到一个更为具体(less generic)的类型的组分上,该复合类型便被认为发生了逆变,也即是说组分只被写入,不被读取。
[warning] Definition Contravariance:
A compound type is considered contravariant if its component types can be assigned to less generic components, i.e. if they are only written, but never read.