类型方差
方差是类型系统中经常出现的一个话题。它用于确定类型参数在子类型方面的行为方式。
¥Variance is a topic that comes up fairly often in type systems. It is used to determine how type parameters behave with respect to subtyping.
首先,我们将设置几个相互扩展的类。
¥First we'll setup a couple of classes that extend one another.
class Noun {}
class City extends Noun {}
class SanFrancisco extends City {}
我们在 泛型类型 部分中看到,可以使用方差符号来描述类型参数何时用于输出位置、何时用于输入位置以及何时用于任一位置。
¥We saw in the section on generic types that it is possible to use variance sigils to describe when a type parameter is used in an output position, when it is used in an input position, and when it is used in either one.
在这里,我们将深入研究其中的每一种情况。
¥Here we'll dive deeper into each one of these cases.
协方差
¥Covariance
例如考虑类型
¥Consider for example the type
type CovariantOf<X> = {
+prop: X;
getter(): X;
}
这里,X 严格出现在输出位置:它用于通过属性访问 o.prop 或通过调用 o.getter() 从类型 CovariantOf<X> 的对象 o 中读取信息。
¥Here, X appears strictly in output positions: it is used to read out information
from objects o of type CovariantOf<X>, either through property accesses o.prop,
or through calls to o.getter().
值得注意的是,鉴于 prop 是只读属性,无法通过对对象 o 的引用来输入数据。
¥Notably, there is no way to input data through the reference to the object o,
given that prop is a readonly property.
当这些条件成立时,我们可以在 CovariantOf 的定义中使用印记 + 来注释 X:
¥When these conditions hold, we can use the sigil + to annotate X in the definition
of CovariantOf:
type CovariantOf<+X> = {
+prop: X;
getter(): X;
}
这些条件对于我们处理 CovariantOf<T> 类型对象的子类型的方式有重要影响。提醒一下,子类型规则可以帮助我们回答这个问题:“给定一些需要 T 类型值的上下文,传入 S 类型值是否安全?” 如果是这种情况,那么 S 是 T 的子类型。
¥These conditions have important implications on the way that we can treat an object
of type CovariantOf<T> with respect to subtyping. As a reminder, subtyping rules
help us answer the question: "given some context that expects values of type
T, is it safe to pass in values of type S?" If this is the case, then S is a
subtype of T.
使用我们的 CovariantOf 定义,并且假设 City 是 Noun 的子类型,那么 CovariantOf<City> 也是 CovariantOf<Noun> 的子类型。的确
¥Using our CovariantOf definition, and given that City is a subtype of Noun, it is
also the case that CovariantOf<City> is a subtype of CovariantOf<Noun>. Indeed
当需要
Noun类型的属性时,读取City类型的属性prop是安全的,并且¥it is safe to read a property
propof typeCitywhen a property of typeNounis expected, and当调用
getter()时,当需要Noun类型的值时,返回City类型的值是安全的。¥it is safe to return values of type
Citywhen callinggetter(), when values of typeNounare expected.
将这两者结合起来,每当需要 CovariantOf<Noun> 时,使用 CovariantOf<City> 总是安全的。
¥Combining these two, it will always be safe to use CovariantOf<City> whenever a
CovariantOf<Noun> is expected.
使用协方差的一个常用示例是 $ReadOnlyArray<T>。就像 prop 属性一样,不能使用 $ReadOnlyArray 引用将数据写入数组。这允许更灵活的子类型规则:Flow 只需证明 S 是 T 的子类型即可确定 $ReadOnlyArray<S> 也是 $ReadOnlyArray<T> 的子类型。
¥A commonly used example where covariance is used is $ReadOnlyArray<T>.
Just like with the prop property, one cannot use a $ReadOnlyArray reference to write data
to an array. This allows more flexible subtyping rules: Flow only needs to prove that
S is a subtype of T to determine that $ReadOnlyArray<S> is also a subtype
of $ReadOnlyArray<T>.
不可变性
¥Invariance
让我们看看如果我们尝试放宽对 X 使用的限制,并使 prop 成为读写属性,会发生什么情况。我们得到类型定义
¥Let's see what happens if we try to relax the restrictions on the use of X and make,
for example, prop be a read-write property. We arrive at the type definition
type NonCovariantOf<X> = {
prop: X;
getter(): X;
};
我们还声明一个类型为 NonCovariantOf<City> 的变量 nonCovariantCity
¥Let's also declare a variable nonCovariantCity of type NonCovariantOf<City>
declare const nonCovariantCity: NonCovariantOf<City>;
现在,将 nonCovariantCity 视为 NonCovariantOf<Noun> 类型的对象是不安全的。如果我们允许这样做,我们可以有以下声明:
¥Now, it is not safe to consider nonCovariantCity as an object of type NonCovariantOf<Noun>.
Were we allowed to do this, we could have the following declaration:
const nonCovariantNoun: NonCovariantOf<Noun> = nonCovariantCity;
该类型允许进行以下分配:
¥This type permits the following assignment:
nonCovariantNoun.prop = new Noun;
这将使 nonCovariantCity 的原始类型无效,因为它现在将在 prop 字段中存储 Noun。
¥which would invalidate the original type for nonCovariantCity as it would now be storing
a Noun in its prop field.
NonCovariantOf 与 CovariantOf 定义的区别在于,类型参数 X 在输入和输出位置中均使用,因为它用于读取和写入属性 prop。这样的类型参数称为不变的,并且是方差的默认情况,因此不需要预先添加符号:
¥What distinguishes NonCovariantOf from the CovariantOf definition is that type parameter X is used both
in input and output positions, as it is being used to both read and write to
property prop. Such a type parameter is called invariant and is the default case
of variance, thus requiring no prepending sigil:
type InvariantOf<X> = {
prop: X;
getter(): X;
setter(X): void;
};
假设一个变量
¥Assuming a variable
declare const invariantCity: InvariantOf<City>;
在以下情况下使用 invariantCity 是不安全的:
¥it is not safe to use invariantCity in a context where:
需要
InvariantOf<Noun>,因为我们不应该能够将Noun写入属性prop。¥an
InvariantOf<Noun>is needed, because we should not be able to write aNounto propertyprop.需要
InvariantOf<SanFrancisco>,因为读取prop可能会返回City,而City可能不是SanFrancisco。¥an
InvariantOf<SanFrancisco>is needed, because readingpropcould return aCitywhich may not beSanFrancisco.
换句话说,InvariantOf<City> 既不是 InvariantOf<Noun> 的子类型,也不是 InvariantOf<SanFrancisco> 的子类型。
¥In orther words, InvariantOf<City> is neither a subtype of InvariantOf<Noun> nor
a subtype of InvariantOf<SanFrancisco>.
逆变
¥Contravariance
当类型参数仅用于输入位置时,我们说它以逆变方式使用。这意味着它只出现在我们将数据写入结构的位置。我们使用 - 来描述这种类型参数:
¥When a type parameter is only used in input positions, we say that it is used in
a contravariant way. This means that it only appears in positions through which
we write data to the structure. We use the sigil - to describe this kind of type
parameters:
type ContravariantOf<-X> = {
-prop: X;
setter(X): void;
};
常见的逆变位置是只写属性和 "设置器" 函数。
¥Common contravariant positions are write-only properties and "setter" functions.
只要需要 ContravariantOf<SanFrancisco> 类型的对象,就可以使用 ContravariantOf<City> 类型的对象,但当需要 ContravariantOf<Noun> 类型的对象时则不能。换句话说,ContravariantOf<City> 是 ContravariantOf<SanFrancisco> 的子类型,但不是 ContravariantOf<Noun>。这是因为将 SanFrancisco 写入可以写入任何 City 的属性中是可以的,但只写入任何 Noun 是不安全的。
¥An object of type ContravariantOf<City> can be used whenever an object of type
ContravariantOf<SanFrancisco> is expected, but not when a ContravariantOf<Noun> is.
In other words, ContravariantOf<City> is a subtype of ContravariantOf<SanFrancisco>, but not
ContravariantOf<Noun>.
This is because it is fine to write SanFrancisco into a property that can have any City written
to, but it is not safe to write just any Noun.