A lot of people seem to not know this. In particular 90% of use of self types I’ve seen appear to exist solely because people do not know this.
Observe the following interpreter session:
scala> class Foo; defined class Foo scala> class Bar; defined class Bar scala> trait Stuff extends Foo; defined trait Stuff scala> new Foo with Stuff; res0: Foo with Stuff = [email protected] scala> new Bar with Stuff; <console>:8: error: illegal inheritance; superclass Bar is not a subclass of the superclass Foo of the mixin trait Stuff new Bar with Stuff; ^
You can basically view this as putting a constraint on the trait, saying that all classes that implement this trait must extend this superclass. This can be particularly useful for adding various sorts of behaviour to classes. e.g. traits which add behaviours to GUI components.
Thus ends our public service announcement.
Pingback: mein Blog » Blog Archive » Scala Quick - links
The distinction between self-types and inheritance sometimes eludes me. Perhaps I’m missing some meaningful difference?
Is there a good reason why Scala needs self-types? Wouldn’t it be possible to have a language where any type – even an abstract type or type parameter – could appear after ‘extends’?
In all honesty, I don’t use self types. 90% of their uses are equivalent to inheritance and I consider the remaining 10% to be antipatterns. Their primary benefit is that inheritance is forced to form a DAG, but self types allow cycles.
Actually, I guess that’s not true. There’s a 1% of use cases involving, as you say, type parameters and members. But I don’t think I’ve ever seen that done in the wild.
Cycles: I think I see it now. Self-types let you break a BigMess up into a lot of smaller traits, all with self-types saying “I’m part of BigMess”. Then BigMess can inherit all the parts, closing the cycles.
I think I even did that once, but I refactored to a DAG after the next time I had to read the code.
Yeah. The problem is that breaking it up like that doesn’t actually reduce the complexity of the BigMess. It just lets you pretend it does until you’re actually forced to deal with it.
In practice, I only use self-types for cake. In some ways it’s just a matter of emphasis. I use inheritance for “is-a” relationships, and self-types for “depends-on” relationships. Using mix-in inheritance for component composition obscures this distinction, but separating them this way allows a way to document the difference.
Self-types have an important benefit that is missed in an analysis of whether or not Scala needs them to express a particular intent. Dave’s point is one part in that he clearly explains the value of self-types for distinctly identifying is-a vs depends-on.
Using inheritance in place of self-types for depends-on relationships has an important drawback: it exposes the dependencies to client code of the component with dependencies. If Stuff is intended to provide purely encapsulated behaviour, then exposing it to client code is a design flaw and self-types should be preferred. If Stuff intentionally provides an extended public API to client code then inheritance has to be used.
Clearly inheritance can be used for both cases, but it strikes me as bad design to use it for dependency management.
I agree with Dave and Alain O’Dea that self types are very useful. Alain O’Dea’s explanation is very good! Of course one should only use self types when one understands that they’re appropriate to use, not just because they happen to be there…
Pingback: Best of drmaciver.com | David R. MacIver