个人TypeScript学习(12)---协变与逆变

协变与逆变

原文链接: what are covariance and contravariance

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

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

  • 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) 的类型是否安全?

不安全,因为在f内调用它的参数(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 中,数组既是可变的,又是协变的。当然,这并不安全。

参考协变与逆变

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值