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
.