How does Scala type inference work with type restrictions?

When type constraints exist in a type parameter, how exactly does Scala determine the type of output? For instance:

def onMouseClicked_=[T >: MouseEvent](lambda: T => Unit) = setOnMouseClicked(new EventHandler[T] { override def handle(event: T): Unit = lambda(event) }) 

When trying to use this function, for example, in:

 onMouseClicked = { me => doSomething() } 

me will be of the MouseEvent output type. A type restriction is an estimate of the lower type, so T must be either a MouseEvent type or a MouseEvent supertype, so why does me have a MouseEvent output type? Shouldn't it call the most general type?

I don’t understand something about how Scala type output works? Or is my understanding of type restrictions completely wrong?

Edit:

Say we also restrict type T to the Event subtype, where Event is the MouseEvent supertype. 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 , compilation fails with a type error, as expected, because the binding ensures that me must be a supertype of MouseEvent .

And yet, if we do

 onMouseClicked = { me: Any => doSomething() } 

compilation succeeds. Obviously, Any not a subtype of Event , so why does compilation succeed? What is type T ?

+6
source share
2 answers

I will try to try, but I'm not sure that everything will be super correct or clear anyway.

Premise

The first point for the address is that a function from type T for the result R ( T => R ) is covariant in return type and contravariant in its parameters: i.e. Function[-T, +R] .

To make it short, this means that for the fsub function for a subtype of f its return type must be of the same type or subtype R (covariant), and its parameter must be the same or supertype T (contravariant).

Let's try to figure this out: if you want to use fsub , where f is expected (see Liskov), you need fsub , which can handle no more than the argument T , but no more specifically, because the callers f expected to go to it. Moreover, since a T will be transmitted, you can be more relaxed in parameters for your fsub and process one of its supertypes, since each T transmitted to it is also an instance of its supertypes.

This is what we mean when we say that the argument of a function is contravariant in its type: it tells you how its type can change depending on the subtyping of the function as a whole, or vice versa.


Given this, we return to the specific case. The handler mutant ( onMouseClicked_= ) should at least accept a lambda of type MouseEvent => Unit . Give it a name, handler: MouseEvent => Unit .

But this does not end here, you should expect that you can pass the method a subtype of this lambda, and what will be the type of this "subfunction"? As we already said, it can accept MouseEvent or any of its supertypes, then subhandler: T => Unit with T :> MouseEvent .

And this is exactly the general form of the method that you saw.

Conclusion

Hopefully it has now become clear that the reason the method defines T as a (non-strict) Supertype of MouseEvent is not because your handler will ever get anything other than MouseEvent , but because you can pass it a lambda that could only handle a more abstract type of event ... (for example, you can use me: Any => doSomething() )

+2
source

I am not entirely sure of the accuracy of this answer, but here goes ...

The lamba type is T => Unit , which is the sugar for Function1[T, Unit] , which is defined as:

 trait Function1[-T1, +R] 

T used for the first parameter T1 , which is contravariant, indicated by -T1 . For a contravariant parameter, the lowest type in the hierarchy is the most common.

+1
source

Source: https://habr.com/ru/post/986297/


All Articles