Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does Scala's type inference work with type bounds?

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?

like image 398
Benedict Lee Avatar asked Apr 30 '15 11:04

Benedict Lee


2 Answers

I'll give this a try, but I'm not sure everything will be supercorrect or anyway clear.

Premise

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.

Conclusion

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() )

like image 127
pagoda_5b Avatar answered Oct 22 '22 13:10

pagoda_5b


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.

like image 43
Chris Martin Avatar answered Oct 22 '22 14:10

Chris Martin