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]
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
.
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.