You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
不安全。因为 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 的位置颠倒过来了)。
协变与逆变
子类型 在编程理论上是一个复杂的话题,而他的复杂之处来自于一对经常会被混淆的现象,我们称之为协变与逆变。这篇文章将会解释上述两个概念。
开始文章之前我们先约定如下的标记:
A ≼ B
意味着A
是B
的子类型。A → B
指的是以A
为参数类型,以B
为返回值类型的函数类型。x : A
意味着x
的类型为A
。一个有趣的问题
假设我有如下三种类型:
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)
的类型是否安全?不安全。因为参数
(g)
有可能是一个不同于灰狗但又是狗的子类,例如GermanShepherd
(牧羊犬)。2. 我们假设
g : Greyhound → Animal
,f(g)
的类型是否安全?不安全。理由同(1)。
3. 我们假设
g : Animal → Animal
,f(g)
的类型是否安全?不安全。因为
f
有可能在调用完参数之后,让返回值,也就是Animal
(动物)狗叫。并非所有动物都会狗叫。4. 我们假设
g : Animal → Greyhound
,f(g)
的类型是否安全?是的,它的类型是安全的。首先,
f
可能会以任何狗的品种来作为参数调用,而所有的狗都是动物。其次,它可能会假设结果是一条狗,而所有的灰狗都是狗。展开讲讲?
如上所述,我们得出结论:
返回值类型很容易理论:灰狗是狗的子类。但参数类型则是相反的:动物是狗的父类!
用合适的术语来描述这个奇怪的表现,可以说我们允许一个函数类型中,返回值类型是协变的,而参数类型是逆变的。返回值类型是协变的,意思是
A ≼ B
就意味着(T → A) ≼ (T → B)
。参数类型是逆变的,意思是A ≼ B
就意味着(B → T) ≼ (A → T)
(A
和B
的位置颠倒过来了)。一个有趣的现象:在
TypeScript
中, 参数类型是双向协变的,也就是说既是协变又是逆变的,而这并不安全。但是现在你可以在
TypeScript 2.6
版本中通过--strictFunctionTypes
或--strict
标记来修复这个问题。那其他类型呢?
问题:
List<Dog>
能否为List<Animal>
的子类?答案有点微妙。如果列表是不可变的(immutable),那么答案是肯定的,因为类型很安全。但是假如列表是可变的,那么答案绝对是否定的!
原因是,假设我需要一串
List<Animal>
而你传给我一串List<Dog>
。由于我认为我拥有的是一串List<Animal>
,我可能会尝试往列表插入一只Cat
。那么你的List<Dog>
里面就会有一只猫!类型系统不应该允许这种情况发生。总结一下,我们可以允许不变的列表(immutable)在它的参数类型上是协变的,但是对于可变的列表(mutable),其参数类型则必须是不变的(invariant),既不是协变也不是逆变。
一个有趣的现象:在
Java
中,数组既是可变的,又是协变的。当然,这并不安全。The text was updated successfully, but these errors were encountered: