981377660LMT / ts

ts学习
6 stars 1 forks source link

协变逆变的例子 #509

Open 981377660LMT opened 3 months ago

981377660LMT commented 3 months ago

直觉上来说,只要有协变就够了:

薛定谔想要一个笼子,里面装着一种动物,他不关心是什么动物(Cage),你给薛定谔一只装着猫的笼子(Cage),薛定谔把这个猫当作一种动物做实验;

也就是说在需要 Cage 的地方都可以给一个 Cage

然而,这显然是不对的!

考虑这样一个情况:

let cage: Cage<Cat> = new Cage();
function capture(x: Cage<Animal>) {
    x.inner = new Dog();
}
capture(cat);

因为协变规则,对 capture 来说笼子是: Cage,往笼子里塞一个狗,完全没问题;

但是对于调用者来说,笼子的类型还是 Cage,这就破坏了类型安全:你接下来的代码期望这是装猫的笼子,其实里面装了一个狗!

981377660LMT commented 3 months ago

所以:如果一个容器是只读的,才能协变,不然很容易就能把一些特殊的容器协变到更一般的容器,再往里面塞进不应该塞的类型;

981377660LMT commented 3 months ago

那么对于可读又可写的类型,当然就是不变了:我们不能做出任何假定,不然有可能爆炸;

981377660LMT commented 3 months ago

类型构造符→对输入类型是逆变的而对输出类型是协变的;

981377660LMT commented 3 months ago

函数f 可以安全替换函数g,如果与函数g 相比,函数f 接受更一般的参数类型,返回更特化的结果类型;

这样的函数才是最厉害的!

981377660LMT commented 3 months ago

Rust中有 lifetime,lifetime是和通常类型平行的另一套类型(另一个范畴),而Rust中的子类型就是对于lifetime而言的!

981377660LMT commented 3 months ago
interface Animal {
  name: string
}
interface Dog extends Animal {
  breed: string
}

type VisitFn<E extends Animal = Animal> = (animal: E) => void

let visitDog: VisitFn = (dog: Dog): void => {}

// !不能将 “(dog: Dog) => void”分配给类型“VisitFn<Animal>”。

type BivariantVisitFn<E extends Animal = Animal> = { bivarianceHack(animal: E): void }['bivarianceHack']

let visitDog2: BivariantVisitFn = (dog: Dog): void => {}

class Zoo {
  visitFn = (animal: Animal) => {}
}

class DogZoo extends Zoo {
  override visitFn = (dog: Dog) => {} // 参数“dog”和“animal” 的类型不兼容。
  visitFn2 = <E extends Animal>(animal: E) => {} // ok
}
981377660LMT commented 3 months ago
image