Methods are contravariant in their argument types. Let me take your example and explain:
val b: B = new B
val y: Y = new Y
b.run(y) // everything is ok
val a: A = b // B is A
val x: X = new X {}
a.run(x) // run on A takes an X. x is of type X
// But: B only takes a Y (more specific type) --> Boom
Let me formulate this in words: In order to support the interface defined in A
, B
needs to be able to work with any X
in its run
method. So you cannot know for sure the X
it is going to get is also a Y
.
Note that you are actually allowed to do the opposite:
class B extends A { def run(x: Any) = /* snip */ }
Since B
accepts any value in its run method, it also accepts an X
, so everything is fine.
It is interesting to see, that return values are the other way around:
trait A {
def foo: X
}
class B extends A {
def foo: Y = /* snip */
}
This is valid, since B
just has to return some X
. Any Y
is also a X
, so we are good.
I hope this made it a bit clearer what is going on here and why the compiler forbids you implement your methods that way.