Can anyone explain the subtype(<:) in the following code? Why it could be used like that? When we use that? Thanks.
trait SwingApi {
type ValueChanged <: Event
val ValueChanged: {
def unapply(x: Event): Option[TextField]
}
type ButtonClicked <: Event
val ButtonClicked: {
def unapply(x: Event): Option[Button]
}
type TextField <: {
def text: String
def subscribe(r: Reaction): Unit
def unsubscribe(r: Reaction): Unit
}
type Button <: {
def subscribe(r: Reaction): Unit
def unsubscribe(r: Reaction): Unit
}
}
I know that code! :)
So let's make sure you understand what <:
means, just in case. A <: B
means that A
must be a subtype of B
, or, to put it in other words, every instance of A
will be an instance of B
as well (but not vice versa).
We know, for example, that every java class is <: Object
(such as String <: Object
).
Next, why type ValueChanged <: Event
. This is usually found in the cake pattern, but I'll skip an explanation of that (the lesson did mention the cake pattern, and provided a link iirc).
What that means is that for anything that extends SwingApi
, the type ValueChanged
must be a subtype of Event
. That makes it possible to call Event
methods on anything that is declared with the type ValueChanged
, without knowing beforehand exactly what type is that.
That is similar to the next use:
type TextField <: {
def text: String
def subscribe(r: Reaction): Unit
def unsubscribe(r: Reaction): Unit
}
We are declaring here that TextField
should have those methods, so when we get something of the type TextField
(such as returned by the ValueChanged
extractor), we can call these methods on it. We could write code like this:
trait MyStuff extends SwingApi {
def subscribeTo(event: ValueChanged) = event match {
case ValueChanged(textField) => textField.subscribe(myReaction)
}
def myReaction: Reaction
}
At this point, neither SwingApi
nor MyStuff
know what types are going to be used for ValueChanged
or TextField
, and, yet, they can use them in normal code.
One interesting fact that is often overlooked about type
declarations is that they can be overridden by classes. That is, I can write something like this:
class SwingImpl extends SwingApi {
class TextField {
def text: String = ???
def subscribe(r: Reaction): Unit = ???
def unsubscribe(r: Reaction): Unit = ???
}
// etc
}
Finally, you might wonder what use is this. I'll give one example. Naturally, you want the production code to show graphical elements on the screen and such, and, perhaps, you could write a separate class that implements it in a web server. But, and I think course takes advantage of it, you could write the class that implements it not as something that displays these components, but as test classes, that verify that the interaction with these components is being done correctly.
That is, you can have a SwingImpl
that extends SwingApi
and show the stuff on your desktop, and a SwingTest
that also extends SwingApi
, but simply let people verify what is being done.