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=>AnyRef
和 getOwnerInfo
是 Car=>AnyRef
当您尝试实现 printCarInfo 时,我们可以处理任何参数,不仅是汽车,还包括卡车和自行车。
显然调用 Printer.printCarInfo(getOwnerInfo)
失败,因为您试图从货车或自行车获取 OwnerInfo,而您的方法实现只能处理汽车。
希望这很清楚。
所以关于你的代码。相反:您可以分配 f4: MysSubClass => Boolean = f
并且它会起作用。
我是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=>AnyRef
和 getOwnerInfo
是 Car=>AnyRef
当您尝试实现 printCarInfo 时,我们可以处理任何参数,不仅是汽车,还包括卡车和自行车。
显然调用 Printer.printCarInfo(getOwnerInfo)
失败,因为您试图从货车或自行车获取 OwnerInfo,而您的方法实现只能处理汽车。
希望这很清楚。
所以关于你的代码。相反:您可以分配 f4: MysSubClass => Boolean = f
并且它会起作用。