2016-10-28 54 views
2
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 

回答

5

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

class Fruit 
class Apple extends Fruit 
class Banana extends Fruit 

class Bhanu[-A](val m: List[A]) // abstract removed for simplicity 

由于Bhanu是contravatiant 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 
} 
+0

我认为你的意思是苹果延伸Fruit而不是Base。 –

+0

@Yuval修复,谢谢。 – dkolmakov

4

dkolmakov's answer做得很好解释为什么这个特殊的例子是行不通的。也许更一般的解释也会有帮助。

对于类型构造函数,函数或特征是否有差异,这意味着什么?按照definition on Wikipedia

在一种编程语言,打字规则或 类型构造的类型系统是:

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

  • 逆变:如果它逆转这个顺序;

  • 如果两者都不适用,则为不变或非变异。

现在,什么是对的类型进行排序?而这个世界是什么意思来保存或逆转排序呢?这意味着,对于任何类型的TU,有要么存在的关系,其中:

  • 协方差: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]

或不变,如果两者之间没有关系。

请注意协方差保留类型之间的顺序,因为CatAnimal派生。现在注意逆变颠倒了顺序,因为现在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返回类型可以处理Tennis,或FootBallVollyBall