繁体   English   中英

Scala协方差,协方差混淆

[英]Scala covariance, contravariance confusion

我是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中的相应操作需要更少(或相同)并提供更多(或相同)(子类型是自反的,则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值就像在层次结构中向下移动一样。

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

class MyOtherSubClass extends MyClass

你的

val f: (MyClass) => Boolean

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

让我们看一下printCarInfo参数。 它是接受Car作为参数并返回AnyRef的小函数: getCarInfo: 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时,我们可以处理任何参数,不仅是汽车,还包括货车和自行车。 显然,当您尝试从货车或自行车上获取OwnerInfo时,对Printer.printCarInfo(getOwnerInfo)调用失败,而您的方法实现只能处理汽车。 希望很清楚。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM