Scala协方差,协方差混淆

时间:2018-07-10 13:02:36

标签: scala covariance contravariance

我是Scala的新手,这真的很混乱。请帮助我。

/**
2    * Remember! In Scala, every function that takes one argument 
3    * is an instance of Function1 with signature:
4    *
5    * trait Function1[-T, +S] extends AnyRef
6    */
7   
8   class Vehicle(val owner: String)
9   class Car(owner: String) extends Vehicle(owner)
10  
11  object Printer {
12
13    val cars = List(new Car("john"), new Car("paul"))
14
15    def printCarInfo(getCarInfo: Car => AnyRef) {
16      for (car <- cars) println(getCarInfo(car))
17    }
18  }
19  
20  object Customer extends App {
21
22   val getOwnerInfo: (Vehicle => String) = _.owner
23   
24   Printer.printCarInfo(getOwnerInfo)
25  }

此代码取自https://medium.com/@sinisalouc/variance-in-java-and-scala-63af925d21dc

规则为:

  

此函数规则的输入类型和   其返回类型中的反变量来自Liskov替代   原则(LSP)。它说如果T支持U的话,它是U的子类型。   与U相同的操作,并且其所有操作所需的操作更少(或相同)   并提供比U中相应操作更多(或相同)的信息   (子类型是自反的,所以S <:S)。

所以我的问题是,如果我可以传递需要超级类型的子类型(代码行号15和22以上),那么为什么以下代码不起作用?

class MyClass extends AnyRef
class MySubClass extends MyClass

abstract class Class {
  val f1: (Any) => Any = ???
  val f2: (Any) => Boolean = ???
  val f3: (MyClass) => Any = ???
  val f4: (MySubClass) => Boolean = ???
  val f5: (Any) => Nothing = ???
  val f6: (MyClass) => Null = ???

  val f: (MyClass) => Boolean = f4; //Error
}

更新 因此,实际上就像将参数传递给函数一样,这样我可以使参数具有协变性[-T]和返回可以协变[+ S]

    class MyClass extends AnyRef
    class MySubClass extends MyClass

    abstract class Class {
      val f1: (Any) => Any = ???
      val f2: (MyClass) => Boolean = ???
      val f3: (MyClass) => Any = ???
      val f4: (MySubClass) => Boolean = ???
      val f5: (Any) => Nothing = ???
      val f6: (MyClass) => Null = ???

      val f: (MySubClass) => AnyVal = f2
}

这是有效的代码,因为MyClass就像在层次结构中向上移动,而Boolean就像在层次结构中向下移动。

2 个答案:

答案 0 :(得分:1)

您的代码将无法编译,因为例如如果您有

class MyOtherSubClass extends MyClass

您的

val f: (MyClass) => Boolean

可以接受MyOtherSubClass作为参数,例如f(new MyOtherSubClass()),但这将调用f4(new MyOtherSubClass())。但是MyOtherSubClass不是MySubClass,因此您将以错误的类型呼叫f4

答案 1 :(得分:1)

让我们调查printCarInfo参数。接受Car作为参数并返回AnyRefgetCarInfo: Car => AnyRef的小函数。

在scala中,可以使用trait Function1[-T, +S] extends AnyRef

表示带有一个参数的此类函数。

Function1-T+S两种类型参数化。第一种类型表示函数的参数。 Car。它是反变数(负号),表示您可以传递任何super-type+S表示返回类型,它是协变量(加号),表示您可以传递任何子类型。

调用

printCarInfo,并传递类型Vehicle => String的参数。 Vehicle是Car的超级类型,而String是AnyRef的子类型,因此它满足条件。

那么,为什么参数在变体位置和返回类型在协变中处于相反的位置。让我们假设参数相反:

printCarInfo接受类型为Vehicle=>AnyRefgetOwnerInfoCar=>AnyRef的函数

当您尝试实现printCarInfo时,我们可以处理任何参数,不仅是汽车,还包括卡车和自行车。 显然,Printer.printCarInfo(getOwnerInfo)的调用失败,因为您试图从货车或自行车上获取所有者信息,而您的方法实现只能处理汽车。 希望很清楚。

因此,关于您的代码。相反:您可以分配f4: MysSubClass => Boolean = f,它将起作用。