Subtype in Scala: what is "type X <: Y"?

9.1k views Asked by At

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
    }

}
2

There are 2 answers

4
Daniel C. Sobral On BEST ANSWER

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.

0
Randall Schulz On

By virtue of the abstract type members ValueChanged and ButtonClicked, trait SwingApi is itself uninstantiable (all traits are, but if they are fully implemented, they are trivially turned into a concrete class that can be instantiated).

These constraints say that instantiable subtypes of SwingApi must define ValueChanged and ButtonClicked as subtypes of Event.

The type aliases TextField and Button are constrained as structural types (they need not particular subclass relationship, but simply must supply the specified members with the specified types).

The why of it is simply generality. This imposes the minimal constraints on the implementors of trait SwingApi necessary for it to be used by code that demands a SwingApi.