6

您能否使用小而简单的 TypeScript 示例解释什么是方差、协方差、逆变和双方差?

[持续更新]

有用的链接:

  1. Oleg Valter的另一个与该主题相关的好答案

  2. Titian-Cernicova-Dragomir对*-riance 的很好解释

  3. 斯蒂芬博耶博客

  4. Scala 文档- 用例子很好的解释

  5. @Titian的回答1

  6. @Titian的回答2

  7. 弗拉德·里斯库蒂亚的博客

  8. 马克·西曼的文章

  9. @jcalz解释

4

1 回答 1

10

方差与泛型类型相对于其类型参数的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>既不随 也不随 变化TF<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!
    }
    

双变量意味着F<T>随着和反对变化:在 中既是协变的也不是逆变的。在健全的类型系统中,这对于任何非平凡的类型函数基本上都不会发生。您可以证明只有像这样的常量类型函数才是真正的双变量(快速草图:对于 all ,so和,并且在健全的类型系统中 if和,then与 相同。所以 if = for all ,then是常量) .TF<T>Ttype F<T> = stringT extends unknownTF<T> extends F<unknown>F<unknown> extends TA extends BB extends BABF<T>F<unknown>TF<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 代码链接

于 2021-02-28T16:49:26.793 回答