Scala Variance Concept, why it doesn't compile

2019-08-22 17:55发布

问题:

I am a noob to Scala so please don't negative vote.

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)

Why line (1) compiles and line (2) does not? To me they both are same as Sequence is subtype of List. How to make it work? like in case of Line 1?

https://docs.scala-lang.org/tutorials/FAQ/collections.html [List object hierarchy]

回答1:

What you see is called contravariance. Function parameters need to be contravariant because of this:

class HisSubClass extends MyClass

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

Now f4 would be called with something which is not MySubClass, which would be error.

The case of Seq / List works because it is the other way around. List is subclass of Seq.

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

is like

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

Promise (contract)

What helped me a lot to understand the parameter / return value variance was to think about type declarations as promises (or contracts). Return value is covariant, because you have promised your return value will be of type MyClass, and by providing MySubClass in a subclass you are still keeping your promise. Promising you will accept the parameter of the type MyClass and then trying to declare a subclass member accepting only MySubClass means trying to narrow down the promise, which you cannot (subclass must fully implement the parent class).

With your example in f4 you have promised the function will be given MySubClass as a parameter. When you try to assign this to f7 you are trying to break this promise, as you could pass any MyClass to f4 by calling it via f7.



回答2:

Because you try to assign to value of type Function1[MyClass, Boolean] value of type Function1[MyClass, Boolean], but first type parameter of Function1 is contravariant, see API doc:

trait Function1[-T1, +R] extends AnyRef

But it allows you do this:

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

You can find the explanation of variances here.



标签: scala concept