Skip to main content

类型方差

方差是类型系统中经常出现的一个话题。它用于确定类型参数在子类型方面的行为方式。

¥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 类型值是否安全?” 如果是这种情况,那么 ST 的子类型。

¥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 定义,并且假设 CityNoun 的子类型,那么 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 type City when a property of type Noun is expected, and

  • 当调用 getter() 时,当需要 Noun 类型的值时,返回 City 类型的值是安全的。

    ¥it is safe to return values of type City when calling getter(), when values of type Noun 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 只需证明 ST 的子类型即可确定 $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.

NonCovariantOfCovariantOf 定义的区别在于,类型参数 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 a Noun to property prop.

  • 需要 InvariantOf<SanFrancisco>,因为读取 prop 可能会返回 City,而 City 可能不是 SanFrancisco

    ¥an InvariantOf<SanFrancisco> is needed, because reading prop could return a City which may not be SanFrancisco.

换句话说,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.