TypeScript: Covariance & Contravariance
个人复习用,不做科普。
创建两个类,Dog
是Animal
的子类。
class Animal {}
class Dog extends Animal {
bark() {}
}
实现一个判断类型的Type
,判断T
是否是U
的子类。
type Assignable<T, U> = T extends U ? true : false
测试,符合结果。
type R4 = Assignable<Dog, Animal> // true
但将同样的问题放到函数的parameters
上,就发生了与第一直觉不一样的情况。
type ConsumeAnimal = (target: Animal) => Animal
type ConsumeDog = (target: Dog) => Dog
type R1 = Assignable<Son, Father> // false
type R2 = Assignable<Father, Son> // true
type R3 = Assignable<Parameters<Son>, Parameters<Father>> // true
问题出在哪里?
通过Google,找到两个关键词:covariance
和contravariance
- 什么是covariance?
Covariance accept subtype
but doesn’t accept supertype
animal = dog, animal 可以接受一个子类 dog.
- 什么是contravariance?
Contravariance accept supertype
but doesn’t accept subtype
contravariance
将函数ConsumeAnimal
看作为Consumer
,它消费一个类Animal
,保证参数target: Animal
存在Animal的所有属性和方法,该函数绝对访问不会超过Animal
之外的属性。
而ConsumeDog可能会访问到Animal
的属性,但也可能访问到Dog
的属性,这时候已经超过了ConsumeAnimal
的范围,因为它拿到的只能是一个Animal
!
而反过来看,ConsumeDog
消费一个Dog
类,保证参数target: Dog
存在Dog
的所有属性和方法。
如果ConsumeAnimal
所做的行为,同样可以应用到类型Dog
身上,因为Dog
类包含Animal
的所有属性和方法。
用以下代码解释上面的问题:
type ConsumeAnimal = (target: Animal) => Animal
type ConsumeDog = (target: Dog) => Dog
declare const consumeAnimal: ConsumeAnimal
declare const consumeDog: ConsumeDog
consumeAnimal = consumeDog
^^^^^^^^^^^^
// Property 'bark' is missing in type 'Animal' but required in type 'Dog'
consumeAnimal
函数能传入Animal
和Dog
,因为Dog
也能提供所有Animal
的属性。
而将consumeDog
赋值给consumeAnimal
,
当向其传入Animal
时,consumeDog
有可能会访问Animal
上所不存在的属性bark
,超过了Animal
的范围。
而以下代码就没有问题
declare const consumeAnimal: ConsumeAnimal
declare const consumeDog: ConsumeDog
consumeDog = consumeAnimal
ConsumeDog
可以传入Dog
,但不可以传入Animal
,因为Animal
不能保证提供所有Dog
的属性。
所以实际的消费函数consumeAnimal可以非常安全的消费一只Dog
,所以consumeAnimal
可以赋值给consumeDog
。
covariance
但对于对象实例来讲,它们可以作为一个Provider
。和以上函数的情况是相反的。
declare let animal: Animal
declare let dog: Dog
animal = dog
dog = animal
^^^
// Property 'bark' is missing in type 'Animal' but required in type 'Dog'
因为dog
属于animal
的子类,所以dog
可以很安全的赋值给一个animal
类型。因为dog
能过提供所有animal
的属性。
反之,animal
不能安全的赋值给dog
,它可能不包含某些dog
的属性。
总结
上面一堆绕话,是为了理清思路。这里尝试总结一下,供以后回顾。
A ≼ B
表示A是B的子类
T → U
表示一个函数参数类型为T,返回类型为U
那那:
- 对于类「实例」A ≼ B,是covariance。
- 对于函数「返回值」来讲 (T → A) ≼ (T → B),是covariance。
- 对于函数「参数」 (B → T) ≼ (A → T),是contravariance。
其它
还有关于一个List<T>
的invariant
情况(该作者的解释简单明了,所以直接引用过来了):
Could List<Dog>
be a subtype of List<Animal>
?
The answer is a little nuanced. If lists are immutable, then it’s safe to say yes. But if lists are mutable, then definitely not!
Why? Suppose I need a List<Animal>
and you pass me a List<Dog>
. Since I think I have a List<Animal>
, I might try to insert a Cat into it. Now your List<Dog>
has a Cat in it! The type system should not allow this.
Formally: we can allow the type of immutable lists to be covariant in its type parameter, but the type of mutable lists must be invariant (neither covariant nor contravariant) in its type parameter.
参考资料
https://www.stephanboyer.com/post/132/what-are-covariance-and-contravariance https://dmitripavlutin.com/typescript-covariance-contravariance/ https://dev.to/codeoz/how-i-understand-covariance-contravariance-in-typescript-2766