Scala中的逆变和协方差

sou*_*ics 2 scala covariance contravariance

abstract class Bhanu[-A] { val m:List[A] }  
Run Code Online (Sandbox Code Playgroud)

error: contravariant type A occurs in covariant position in type => List[A] of value m
       abstract class Bhanu[-A] { val m:List[A] }
Run Code Online (Sandbox Code Playgroud)

abstract class Bhanu[+A] { val m:List[A] }
Run Code Online (Sandbox Code Playgroud)

defined class Bhanu
Run Code Online (Sandbox Code Playgroud)

我无法绕过这个概念来解释为什么它会因为逆变而失败,而它却成功地进行了协方差.

其次(来自其他一些例子),

该陈述究竟意味着什么?

Function1[Sport,Int] <: Function1[Tennis,Int] since Tennis <: Sport
Run Code Online (Sandbox Code Playgroud)

这对我来说似乎是违反直觉的,不应该是以下情况吗?

Function1[Tennis,Int] <: Function1[Sport,Int] since Tennis <: Sport
Run Code Online (Sandbox Code Playgroud)

dko*_*kov 5

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

class Fruit
class Apple extends Fruit
class Banana extends Fruit

class Bhanu[-A](val m: List[A]) // abstract removed for simplicity
Run Code Online (Sandbox Code Playgroud)

由于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
Run Code Online (Sandbox Code Playgroud)

因此,Scala编译器通过限制在协变位置使用逆变类型参数来保护我们免受此类错误的影响.

对于你的第二个问题,让我们看看这个例子.考虑我们有功能:

val func1: Function1[Tennis,Int] = ...
Run Code Online (Sandbox Code Playgroud)

如果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.
Run Code Online (Sandbox Code Playgroud)

但是,如果我们做Function1逆变其参数,所以Function1[Sport,Int] <: Function1[Tennis,Int]这里Tennis <: Sport比:

val func1: Function1[Tennis,Int] = ...
val func2: Function1[Sport,Int] = func1 // COMPILE TIME ERROR!
Run Code Online (Sandbox Code Playgroud)

对于相反的情况,一切都很好:

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!
Run Code Online (Sandbox Code Playgroud)

函数的参数类型和结果类型中的协变必须是逆变的:

trait Function1[-T, +U] {
  def apply(x: T): U
}
Run Code Online (Sandbox Code Playgroud)


Yuv*_*kov 5

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

一个类型构造函数,一个函数或一个特征具有差异是什么意思?根据维基百科定义

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

  • 协变:如果保留类型(?)的顺序,则将类型从更特定的类型转换为更通用的类型;

  • 相反:如果它颠倒了这种顺序

  • 如果这两个都不适用,则为不变或不变。

现在,什么是类型排序?保留或逆转顺序到底意味着什么?这意味着对于任何类型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]

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

注意,由于a 衍生,协方差如何保留类型之间的顺序。现在请注意,自变量如何推导顺序,因为现在得出了。CatAnimalFunction0[Animal, Unit]Function0[Cat, Unit]

我们如何才能将这种变异概念带给我们的优势?根据这些规则,我们可以概括类型构造函数之间的分配兼容性!最好的例子就是List[A]Option[A]Function1[-T, +U](或任何FunctionN真的)。

让我们以一个同时具有协变和反变参数的Function1[-T, +U]T => U)为例。

为什么输入类型参数是协变量而输出类型是协变量?首先,根据上面定义的公理,我们可以看到:

Function1[Sport,Int] <: Function1[Tennis,Int]
Run Code Online (Sandbox Code Playgroud)

输入类型参数通常颠倒了类型的关系,因为通常是Tennis <: Sport,但是这里是相反的。为什么会这样呢?因为任何接受a的函数Sport都会知道如何处理Tennis,但事实并非如此。例如:

val sportFunc: (Sport => Int) = ???
val tennisFunc: (Tennis => Int) = sportFunc

val result = tennisFunc(new Tennis())
Run Code Online (Sandbox Code Playgroud)

但是,一个期望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()) 
Run Code Online (Sandbox Code Playgroud)

关于协变的输出类型则相反,任何期望a Sport作为返回类型的人都可以处理Tennis,或FootBallVollyBall