协变与逆变 您所在的位置:网站首页 函数的逆是什么意思 协变与逆变

协变与逆变

2024-07-16 15:43| 来源: 网络整理| 查看: 265

关注公众号 关注公众号与我交流 与我交流购买此书 购买此书 # 协变与逆变

原文链接: what are covariance and contravariance

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

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

A ≼ B 意味着 A 是 B 的子类型。 A → B 指的是以 A 为参数类型,以 B 为返回值类型的函数类型。 x : A 意味着 x 的类型为 A。 # 一个有趣的问题

假设我有如下三种类型:

Greyhound ≼ Dog ≼ Animal

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

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

Greyhound → Greyhound Greyhound → Animal Animal → Animal Animal → Greyhound

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

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

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

不安全,因为在f内调用它的参数(g)函数时,使用的参数可能是一个不同于灰狗但又是狗的子类型,例如 GermanShepherd (牧羊犬)。

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

不安全。理由同(1)。

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

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

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

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

# 展开讲讲?

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

(Animal → Greyhound) ≼ (Dog → Dog)

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

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

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

# 那其他类型呢?

问题:List 能否为 List 的子类型?

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

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

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

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

编辑此页 上次更新: 6/30/2022, 12:47:40 PM

← Reflect Metadata infer →



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有