When type bounds exist on a type parameter, how exactly does Scala determine the type to infer? For example:
def onMouseClicked_=[T >: MouseEvent](lambda: T => Unit) =
setOnMouseClicked(new EventHandler[T] {
override def handle(event: T): Unit = lambda(event)
})
When attempting to use this function, such as in:
onMouseClicked = { me => doSomething() }
me
will have the inferred type of MouseEvent
. The type bound of T
is a lower type bound, so T
must either be of type MouseEvent
or a supertype of MouseEvent
, so why does me
have the inferred type of MouseEvent
? Shouldn't it infer the most general type?
Is it something I'm not getting about how Scala's type inference works? Or is my understanding of type bounds completely wrong?
Edit:
Let's say we further restrict the the type of T to be a subtype of Event
, where Event
is a supertype of MouseEvent
. So we get:
def onMouseClicked_=[T >: MouseEvent <: Event](lambda: T => Unit) =
setOnMouseClicked(new EventHandler[T] {
override def handle(event: T): Unit = lambda(event)
})
So if we do
onMouseClicked = { me: MouseDragEvent => doSomething() }
where MouseDragEvent
is a subtype of MouseEvent
, the compile fails with a type error, as expected, because the bound ensures that me
must be a supertype of MouseEvent
.
And yet if we do
onMouseClicked = { me: Any => doSomething() }
the compile succeeds. It's obvious that Any
is not a subtype of Event
, so why does the compile succeed? What is inferred type of T
?
I'll give this a try, but I'm not sure everything will be supercorrect or anyway clear.
The first point to address is that a function from type T
to result R
(T => R
) is covariant in its return type and contravariant in its parameters: i.e. Function[-T, +R]
.
To make it short it means that for function fsub
to be a subtype of function f
its return type must be the same type or a subtype of R
(covariant) and its parameter must be the same or a supertype of T
(contravariant).
Let's try to make sense of this: if you want to be able to use fsub
where a f
is expected (see Liskov), you need an fsub
that can handle at most a passed argument of T
, but nothing more specific, because that's what the callers of f
are expected to pass to it. Moreover, since a T
will be passed, you can be more relaxed on the parameters for your fsub
and handle one of its supertypes, since every T
passed to it is also an instance of its supertypes.
This is what is meant when we say that a function's argument is contravariant in its type: it tells you how its type can change in relationship subtyping the function as a whole, or actually the other way round.
Given this, let's get back to the specific case. The handler's mutator (onMouseClicked_=
) should at least accept a lambda of type MouseEvent => Unit
. Let's give it a name, handler: MouseEvent => Unit
.
But it doesn't end here, you should expect to be able to pass the method a subtype of this lambda, and what will the type of this "subfunction" be? As we said before it could accept a MouseEvent
or any supertype of it, then subhandler: T => Unit
with T :> MouseEvent
.
And that's exactly the general form of the method you saw.
I hope it became clear now that the reason why the method defines T
as a (non-strict) supertype of MouseEvent
is not because your handler will ever receive anything different from a MouseEvent
, but because you can pass it a lambda that could be able to only handle a more abstract type of event... (e.g. you can use a me: Any => doSomething()
)
I'm not entirely confident about the accuracy of this answer, but here goes...
The type of lamba
is T => Unit
, which is sugar for Function1[T, Unit]
, which is defined as:
trait Function1[-T1, +R]
T
is used for the first parameter T1
, which is contravariant, indicated by the -
in -T1
. For a contravariant parameter, the lowest type in the hierarchy is the most general.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With