类型方差
方差是类型系统中经常出现的一个话题。它用于确定类型参数在子类型方面的行为方式。
¥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
prop
of typeCity
when a property of typeNoun
is expected, and当调用
getter()
时,当需要Noun
类型的值时,返回City
类型的值是安全的。¥it is safe to return values of type
City
when callinggetter()
, when values of typeNoun
are 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 aNoun
to propertyprop
.需要
InvariantOf<SanFrancisco>
,因为读取prop
可能会返回City
,而City
可能不是SanFrancisco
。¥an
InvariantOf<SanFrancisco>
is needed, because readingprop
could return aCity
which 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
.