Scala中的逆变和协方差

时间:2016-10-28 07:30:54

标签: scala covariance contravariance

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

2 个答案:

答案 0 :(得分:5)

让我们看看你提到的第一个例子。 考虑一下我们:

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
}

答案 1 :(得分:4)

dkolmakov's answer很好地解释了为什么这个特定的例子不起作用。也许更一般的解释也会有所帮助。

类型构造函数,函数或特征是否有差异意味着什么?根据{{​​3}}:

  

在编程语言的类型系统中,键入规则或   类型构造函数是:

     
      
  • 协变:如果保留类型的排序   (≤),它将类型从更具体到更通用的顺序排序;

  •   
  • 逆变:如果反转此顺序;

  •   
  • 如果这些都不适用,则不变或不变。

  •   

现在,什么是类型的排序?以及维护或逆转排序意味着什么呢?这意味着对于任何类型TU,都存在以下关系:

  • 协方差:T <: U - &gt; M[T] <: M[U] - 例如,Cat <: AnimalList[Cat] <: List[Animal]
  • 逆变法:T <: U - &gt; M[T] >: M[U] - 例如,Cat <: AnimalFunction1[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作为返回类型的人都可以处理TennisFootBall ,或VollyBall