Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【译】协变与逆变 #8

Open
ZhaZhengRefn opened this issue Nov 9, 2018 · 1 comment
Open

【译】协变与逆变 #8

ZhaZhengRefn opened this issue Nov 9, 2018 · 1 comment

Comments

@ZhaZhengRefn
Copy link
Owner

协变与逆变

子类型 在编程理论上是一个复杂的话题,而他的复杂之处来自于一对经常会被混淆的现象,我们称之为协变逆变。这篇文章将会解释上述两个概念。

开始文章之前我们先约定如下的标记:

  • A ≼ B 意味着 AB 的子类型。
  • A → B 指的是以 A 为参数类型,以 B 为返回值类型的函数类型。
  • x : A 意味着 x 的类型为 A

一个有趣的问题

假设我有如下三种类型:

Greyhound ≼ Dog ≼ Animal

Greyhound (灰狗)是 Dog (狗)的子类,而 Dog 则是 Animal (动物)的子类。由于子类型通常是可传递的,因此我们也称 GreyhoundAnimal 的子类。

问题:以下哪种类型是 Dog → Dog 的子类呢?

  1. Greyhound → Greyhound
  2. Greyhound → Animal
  3. Animal → Animal
  4. Animal → Greyhound

让我们来思考一下如何解答这个问题。首先我们假设 f 是一个以 Dog → Dog 为参数的函数。它的返回值并不重要,为了具体描述问题,我们假设函数结构体是这样的: f : (Dog → Dog) → String

现在我想给函数 f 传入某个函数 g 来调用。我们来瞧瞧当 g 为以上四种类型时,会发生什么情况。

1. 我们假设 g : Greyhound → Greyhoundf(g) 的类型是否安全?

不安全。因为参数 (g) 有可能是一个不同于灰狗但又是狗的子类,例如 GermanShepherd (牧羊犬)。

2. 我们假设 g : Greyhound → Animalf(g) 的类型是否安全?

不安全。理由同(1)。

3. 我们假设 g : Animal → Animalf(g) 的类型是否安全?

不安全。因为 f 有可能在调用完参数之后,让返回值,也就是 Animal (动物)狗叫。并非所有动物都会狗叫。

4. 我们假设 g : Animal → Greyhoundf(g) 的类型是否安全?

是的,它的类型是安全的。首先, f 可能会以任何狗的品种来作为参数调用,而所有的狗都是动物。其次,它可能会假设结果是一条狗,而所有的灰狗都是狗。

展开讲讲?

如上所述,我们得出结论:

(Animal → Greyhound) ≼ (Dog → Dog)

返回值类型很容易理论:灰狗是狗的子类。但参数类型则是相反的:动物是狗的父类

用合适的术语来描述这个奇怪的表现,可以说我们允许一个函数类型中,返回值类型是协变的,而参数类型是逆变的。返回值类型是协变的,意思是 A ≼ B 就意味着 (T → A) ≼ (T → B) 。参数类型是逆变的,意思是 A ≼ B 就意味着 (B → T) ≼ (A → T)AB 的位置颠倒过来了)。

一个有趣的现象:在 TypeScript 中, 参数类型是双向协变的
,也就是说既是协变又是逆变的,而这并不安全。但是现在你可以在 TypeScript 2.6 版本中通过 --strictFunctionTypes--strict 标记来修复这个问题。

那其他类型呢?

问题List<Dog> 能否为 List<Animal> 的子类?

答案有点微妙。如果列表是不可变的(immutable),那么答案是肯定的,因为类型很安全。但是假如列表是可变的,那么答案绝对是否定的!

原因是,假设我需要一串 List<Animal> 而你传给我一串 List<Dog> 。由于我认为我拥有的是一串 List<Animal> ,我可能会尝试往列表插入一只 Cat。那么你的 List<Dog> 里面就会有一只猫!类型系统不应该允许这种情况发生。

总结一下,我们可以允许不变的列表(immutable)在它的参数类型上是协变的,但是对于可变的列表(mutable),其参数类型则必须是不变的(invariant),既不是协变也不是逆变。

一个有趣的现象:在 Java 中,数组既是可变的,又是协变的。当然,这并不安全。

原文链接

@ZhaZhengRefn
Copy link
Owner Author

本文从深入浅出typescript的类型兼容性章节了解而来

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant