Scala方差概念,为什么不编译

时间:2018-07-05 11:23:35

标签: scala concept

我是Scala的菜鸟,所以请不要投反对票。

class MyClass extends AnyRef
class MySubClass extends MyClass

val af0: (Seq[_]) => Boolean = (s) ⇒ { s eq null }

val f4: (MySubClass) => Boolean = (s) => { s eq null }

val af2: (List[_]) => Boolean = af0 //(Line 1)
val f7: MyClass => Boolean = f4 //(Line 2)

为什么第(1)行编译而第(2)行不编译?对我来说,它们都与Sequence是List的子类型相同。如何使其运作?像第1行一样?

https://docs.scala-lang.org/tutorials/FAQ/collections.html [列表对象层次结构]

2 个答案:

答案 0 :(得分:3)

您看到的称为contravariance。由于以下原因,函数参数必须是互变的:

class HisSubClass extends MyClass

val his = new HisSubClass 
f7(his) // his is accepted as MyClass

现在f4会被调用,而不是MySubClass,这会出错。

使用Seq / List的情况是可行的,因为反之亦然。 ListSeq的子类。

val af2: (List[_]) => Boolean = af0

就像

val aff0: (MyClass) => Boolean = (s) ⇒ { s eq null }
val aff2: (MySubClass) => Boolean = aff0

承诺(合同)

帮助我理解参数/返回值差异的很多方法是将类型声明视为承诺(或合同)。返回值是协变的,因为您已承诺您的返回值将为MyClass类型,并且通过在子类中提供MySubClass仍会遵守您的承诺。保证您将接受类型为MyClass的参数,然后尝试声明仅接受MySubClass的子类成员意味着试图缩小承诺,而您不能这样做(子类必须完全实现父类)。

以您在f4中的示例为例,您已承诺将以函数MySubClass作为参数。当您尝试将其分配给f7时,您正试图违反此承诺,因为您可以通过MyClass对其进行调用,从而将f4传递给f7

答案 1 :(得分:2)

由于您尝试将类型为Function1[MyClass, Boolean]的值赋给类型Function1[MyClass, Boolean]的值,但是Function1的第一个类型参数是互变的,请参见API doc

trait Function1[-T1, +R] extends AnyRef

但是它允许您这样做:

val f7: MyClass => Boolean = s => s eq null 
val f44: (MySubClass) => Boolean = f7

您可以找到方差here的解释。