Scala 中的逆变和协变
Contravariance and covariance in Scala
abstract class Bhanu[-A] { val m:List[A] }
给予
error: contravariant type A occurs in covariant position in type => List[A] of value m
abstract class Bhanu[-A] { val m:List[A] }
而
abstract class Bhanu[+A] { val m:List[A] }
给予
defined class Bhanu
关于为什么逆变失败而协变成功,我无法理解这个概念。
其次(来自其他一些例子),
该声明的确切含义是什么?
Function1[Sport,Int] <: Function1[Tennis,Int] since Tennis <: Sport
这对我来说似乎违反直觉,不应该是下面的吗?
Function1[Tennis,Int] <: Function1[Sport,Int] since Tennis <: Sport
让我们看看你提到的第一个例子。
考虑我们有:
class Fruit
class Apple extends Fruit
class Banana extends Fruit
class Bhanu[-A](val m: List[A]) // abstract removed for simplicity
因为 Bhanu
是矛盾的 Bhanu[Fruit] <: Bhanu[Apple]
所以你可以这样做:
val listOfApples = new List[Apple](...)
val listOfFruits = listOfApples // Since Lists are covariant in Scala
val a: Bhanu[Fruit] = new Bhanu[Fruit](listOfFruits)
val b: Bhanu[Banana] = a // Since we assume Bhanu is contravariant
val listOfBananas: List[Banana] = b.m
val banana: Banana = listOfBananas(0) // TYPE ERROR! Here object of type Banana is initialized
// with object of type Apple
因此 Scala 编译器通过限制在协变位置使用逆变类型参数来保护我们免受此类错误。
对于你的第二个问题,我们也来看看这个例子。考虑我们有功能:
val func1: Function1[Tennis,Int] = ...
如果 Function1[Tennis,Int] <: Function1[Sport,Int]
其中 Tennis <: Sport
如您所建议,我们可以执行以下操作:
val func2: Function1[Sport,Int] = func1
val result: Int = func2(new Swimming(...)) // TYPE ERROR! Since func1 has argument
// of type Tennis.
但是如果我们在其参数中使 Function1
是逆变的,那么 Function1[Sport,Int] <: Function1[Tennis,Int]
其中 Tennis <: Sport
比:
val func1: Function1[Tennis,Int] = ...
val func2: Function1[Sport,Int] = func1 // COMPILE TIME ERROR!
反之亦然:
val func1: Function1[Sport,Int] = ...
val func2: Function1[Tennis,Int] = func1 // OK!
val result1: Int = func1(new Tennis(...)) // OK!
val result2: Int = func2(new Tennis(...)) // OK!
函数的参数类型必须是逆变的,结果类型必须是协变的:
trait Function1[-T, +U] {
def apply(x: T): U
}
很好地解释了为什么这个特定示例不起作用。也许更一般的解释也会有所帮助。
对于类型构造函数、函数或特征来说,变异是什么意思?根据 definition on Wikipedia:
Within the type system of a programming language, a typing rule or a
type constructor is:
Covariant: if it preserves the ordering of types
(≤), which orders types from more specific to more generic;
Contravariant: if it reverses this ordering;
Invariant or nonvariant if neither of these applies.
现在,什么是类型排序?保留或颠倒顺序究竟意味着什么?这意味着对于任何类型 T
和 U
,都存在以下关系:
- 协方差:
T <: U
-> M[T] <: M[U]
- 例如,Cat <: Animal
,所以List[Cat] <: List[Animal]
- 逆变:
T <: U
-> M[T] >: M[U]
- 例如,Cat <: Animal
,所以Function1[Cat, Unit] >: Function1[Animal, Unit]
如果两者之间没有关系,则不变。
注意 协方差 如何保持类型之间的顺序,因为 Cat
派生 Animal
。现在注意 contravariance 如何反转顺序,因为现在 Function0[Animal, Unit]
派生出 Function0[Cat, Unit]
.
我们如何利用这种差异概念来发挥我们的优势?基于这些规则,我们可以概括类型构造函数之间的赋值兼容性! List[A]
、Option[A]
和 Function1[-T, +U]
(或任何 FunctionN
)都是很好的例子。
让我们以具有协变和逆变参数的 Function1[-T, +U]
(T => U
) 为例。
为什么输入类型参数是逆变的输出类型是协变的?首先,根据上面定义的公理,我们可以看到:
Function1[Sport,Int] <: Function1[Tennis,Int]
输入类型参数颠倒了类型的关系,因为通常Tennis <: Sport
,但这里是相反的。为什么会这样? 因为接受 Sport
的任何函数都知道如何处理 Tennis
,但反之则不然。例如:
val sportFunc: (Sport => Int) = ???
val tennisFunc: (Tennis => Int) = sportFunc
val result = tennisFunc(new Tennis())
但是期望 Tennis
的函数知道如何处理任何 Sport
吗?当然不是:
val tennisFunc: (Tennis => Int) = ???
val sportFunc: (Sport => Int) = tennisFunc
// The underlying function needs to deal with a Tennis, not a `FootBall`.
val result = sportFunc(new FootBall())
对于协变的输出类型,情况恰恰相反,任何期望 Sport
作为 return 类型 的人都可以处理 Tennis
,或FootBall
,或VollyBall
。
abstract class Bhanu[-A] { val m:List[A] }
给予
error: contravariant type A occurs in covariant position in type => List[A] of value m
abstract class Bhanu[-A] { val m:List[A] }
而
abstract class Bhanu[+A] { val m:List[A] }
给予
defined class Bhanu
关于为什么逆变失败而协变成功,我无法理解这个概念。
其次(来自其他一些例子),
该声明的确切含义是什么?
Function1[Sport,Int] <: Function1[Tennis,Int] since Tennis <: Sport
这对我来说似乎违反直觉,不应该是下面的吗?
Function1[Tennis,Int] <: Function1[Sport,Int] since Tennis <: Sport
让我们看看你提到的第一个例子。 考虑我们有:
class Fruit
class Apple extends Fruit
class Banana extends Fruit
class Bhanu[-A](val m: List[A]) // abstract removed for simplicity
因为 Bhanu
是矛盾的 Bhanu[Fruit] <: Bhanu[Apple]
所以你可以这样做:
val listOfApples = new List[Apple](...)
val listOfFruits = listOfApples // Since Lists are covariant in Scala
val a: Bhanu[Fruit] = new Bhanu[Fruit](listOfFruits)
val b: Bhanu[Banana] = a // Since we assume Bhanu is contravariant
val listOfBananas: List[Banana] = b.m
val banana: Banana = listOfBananas(0) // TYPE ERROR! Here object of type Banana is initialized
// with object of type Apple
因此 Scala 编译器通过限制在协变位置使用逆变类型参数来保护我们免受此类错误。
对于你的第二个问题,我们也来看看这个例子。考虑我们有功能:
val func1: Function1[Tennis,Int] = ...
如果 Function1[Tennis,Int] <: Function1[Sport,Int]
其中 Tennis <: Sport
如您所建议,我们可以执行以下操作:
val func2: Function1[Sport,Int] = func1
val result: Int = func2(new Swimming(...)) // TYPE ERROR! Since func1 has argument
// of type Tennis.
但是如果我们在其参数中使 Function1
是逆变的,那么 Function1[Sport,Int] <: Function1[Tennis,Int]
其中 Tennis <: Sport
比:
val func1: Function1[Tennis,Int] = ...
val func2: Function1[Sport,Int] = func1 // COMPILE TIME ERROR!
反之亦然:
val func1: Function1[Sport,Int] = ...
val func2: Function1[Tennis,Int] = func1 // OK!
val result1: Int = func1(new Tennis(...)) // OK!
val result2: Int = func2(new Tennis(...)) // OK!
函数的参数类型必须是逆变的,结果类型必须是协变的:
trait Function1[-T, +U] {
def apply(x: T): U
}
对于类型构造函数、函数或特征来说,变异是什么意思?根据 definition on Wikipedia:
Within the type system of a programming language, a typing rule or a type constructor is:
Covariant: if it preserves the ordering of types (≤), which orders types from more specific to more generic;
Contravariant: if it reverses this ordering;
Invariant or nonvariant if neither of these applies.
现在,什么是类型排序?保留或颠倒顺序究竟意味着什么?这意味着对于任何类型 T
和 U
,都存在以下关系:
- 协方差:
T <: U
->M[T] <: M[U]
- 例如,Cat <: Animal
,所以List[Cat] <: List[Animal]
- 逆变:
T <: U
->M[T] >: M[U]
- 例如,Cat <: Animal
,所以Function1[Cat, Unit] >: Function1[Animal, Unit]
如果两者之间没有关系,则不变。
注意 协方差 如何保持类型之间的顺序,因为 Cat
派生 Animal
。现在注意 contravariance 如何反转顺序,因为现在 Function0[Animal, Unit]
派生出 Function0[Cat, Unit]
.
我们如何利用这种差异概念来发挥我们的优势?基于这些规则,我们可以概括类型构造函数之间的赋值兼容性! List[A]
、Option[A]
和 Function1[-T, +U]
(或任何 FunctionN
)都是很好的例子。
让我们以具有协变和逆变参数的 Function1[-T, +U]
(T => U
) 为例。
为什么输入类型参数是逆变的输出类型是协变的?首先,根据上面定义的公理,我们可以看到:
Function1[Sport,Int] <: Function1[Tennis,Int]
输入类型参数颠倒了类型的关系,因为通常Tennis <: Sport
,但这里是相反的。为什么会这样? 因为接受 Sport
的任何函数都知道如何处理 Tennis
,但反之则不然。例如:
val sportFunc: (Sport => Int) = ???
val tennisFunc: (Tennis => Int) = sportFunc
val result = tennisFunc(new Tennis())
但是期望 Tennis
的函数知道如何处理任何 Sport
吗?当然不是:
val tennisFunc: (Tennis => Int) = ???
val sportFunc: (Sport => Int) = tennisFunc
// The underlying function needs to deal with a Tennis, not a `FootBall`.
val result = sportFunc(new FootBall())
对于协变的输出类型,情况恰恰相反,任何期望 Sport
作为 return 类型 的人都可以处理 Tennis
,或FootBall
,或VollyBall
。