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

规则是:

This rule of function being covariant in its input type and contravariant in its return type comes from the Liskov substitution principle (LSP). It says that T is a subtype of U if it supports the same operations as U and all of its operations require less (or same) and provide more (or same) than the corresponding operations in U (subtyping is reflexive so 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] 并且 return 可以是协变的 [+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 作为参数和 returns AnyRef 的小函数:getCarInfo: Car => AnyRef.

在 scala 中,这种只有一个参数的函数可以用 trait Function1[-T, +S] extends AnyRef

表示

Function1-T+S两种类型参数化。第一种类型表示函数的参数。 Car 在我们的案例中。而且它是反变体(减号),这意味着你可以传递任何 super-type+S 表示 return 类型并且它是协变的(加号),这意味着您可以传递任何子类型。

调用

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

那么为什么参数处于对变位置而 return 类型处于协变。让我们尝试假设参数相反:

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

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

所以关于你的代码。相反:您可以分配 f4: MysSubClass => Boolean = f 并且它会起作用。