TypeScript中的方差、协方差、逆变和双方差的区别
您能否使用小而简单的 TypeScript 示例来解释什么是方差、协方差、逆变和双方差?
[持续更新]
有用的链接:
-
Oleg Valter 的另一个与该主题相关的好答案
-
Titian-Cernicova-Dragomir对*-rianance 的很好解释
-
斯蒂芬博耶博客
-
Scala 文档- 用例子很好的解释
-
@Titian 的回答 1
-
@Titian 的回答 2
-
Vlad Riscutia 的博客
-
马克西曼的文章
回答
差异有一个泛型类型如何做F<T> 变化相对于它的类型参数T。如果您知道T extends U,那么方差会告诉您是否可以得出结论F<T> extends F<U>,得出结论F<U> extends F<T>,或者两者都不能,或者两者都可以。
协方差意味着F<T>与T 合作-而变化。也就是说,F<T> 随(在同一方向) 变化T。换句话说,如果T extends U,则F<T> extends F<U>。例子:
-
函数或方法类型随它们的返回类型而变化:
type Co<V> = () => V; function covariance<U, T extends U>(t: T, u: U, coT: Co<T>, coU: Co<U>) { u = t; // okay t = u; // error! coU = coT; // okay coT = coU; // error! }
其他(暂时未说明)示例是:
- 对象在它们的属性类型上是协变的,即使这对于可变属性来说并不合理
- 类构造函数的实例类型是协变的
逆变意味着F<T>与T 禁忌-而变化。即,F<T> 变化的计数器(在从相反方向)T。换句话说,如果T extends U,则F<U> extends F<T>。例子:
-
函数类型与其参数类型相反(
--strictFunctionTypes启用):type Contra<V> = (v: V) => void; function contravariance<U, T extends U>(t: T, u: U, contraT: Contra<T>, contraU: Contra<U>) { u = t; // okay t = u; // error! contraU = contraT; // error! contraT = contraU; // okay }
其他(暂时未说明)示例是:
- 对象的键类型是逆变的
- 类构造函数的构造参数类型是逆变的
不变性意味着F<T>既不随 也不随 变化T:F<T>在 中既不协变也不逆变T。这实际上是最一般情况下发生的情况。协变和逆变是“脆弱的”,因为当您结合协变和逆变类型函数时,很容易产生不变的结果。例子:
-
返回与其参数相同类型的函数类型在该类型中既不共变也不反变:
type In<V> = (v: V) => V; function invariance<U, T extends U>(t: T, u: U, inT: In<T>, inU: In<U>) { u = t; // okay t = u; // error! inU = inT; // error! inT = inU; // error! }
Bivariance意味着F<T>变化既与和反对T:F<T>既是协也不逆变在T。在健全的类型系统中,对于任何非平凡的类型函数,这基本上不会发生。您可以证明只有像这样的常量类型函数type F<T> = string才是真正的二元函数(快速草图:T extends unknown对 all T、soF<T> extends F<unknown>和都是真实的F<unknown> extends T,并且在声音类型系统中 if A extends Band B extends B, thenA与 相同B。所以如果F<T>= F<unknown>for all T,则F<T>是常量) .
但是 Typescript 没有也不打算拥有一个完整的类型系统。还有一个值得注意的案例,TypeScript 将类型函数视为二元函数:
-
方法类型与它们的参数类型同时变化和相反(这也发生在所有
--strictFunctionTypes禁用的函数类型中):type Bi<V> = { foo(v: V): void }; function bivariance<U, T extends U>(t: T, u: U, biT: Bi<T>, biU: Bi<U>) { u = t; // okay t = u; // error! biU = biT; // okay biT = biU; // okay }
Playground 链接到代码