A self-type for a trait A
:
trait B
trait A { this: B => }
says that "A
cannot be mixed into a concrete class that does not also extend B
".
On the other hand, the following:
trait B
trait A extends B
says that "any (concrete or abstract) class mixing in A
will also be mixing in B".
Don't these two statements mean the same thing? The self-type seems to serve only to create the possibility of a simple compile-time error.
What am I missing?
Let's start with the cyclical dependency.
However, the modularity of this solution is not as great as it might first appear, because you can override self types as so:
Although, if you override a member of a self type, you lose access to the original member, which can still be accessed through super using inheritance. So what is really gained over using inheritance is:
Now I can't claim to understand all the subtleties of the cake pattern, but it strikes me that the main method of enforcing modularity is through composition rather than inheritance or self types.
The inheritance version is shorter, but the main reason I prefer inheritance over self types is that I find it much more tricky to get the initialisation order correct with self types. However, there are some things you can do with self types that you can't do with inheritance. Self types can use a type while inheritance requires a trait or a class as in:
You can even do:
Although you'll never be able to instantiate it. I don't see any absolute reason for not being be able to inherit from a type, but I certainly feel it would be useful to have path constructor classes and traits as we have type constructor traits / classes. As unfortunately
We have this:
Or this:
One point that should be empathised more is that traits can extends classes. Thanks to David Maclver for pointing this out. Here's an example from my own code:
ScnBase
inherits from the Swing Frame class, so it could be used as a self type and then mixed in at the end (at instantiation). However,val geomR
needs to be initialised before it's used by inheriting traits. So we need a class to enforce prior initialisation ofgeomR
. The classScnVista
can then be inherited from by multiple orthogonal traits which can themselves be inherited from. Using multiple type parameters (generics) offers an alternative form of modularity.It is predominately used for Dependency Injection, such as in the Cake Pattern. There exists a great article covering many different forms of dependency injection in Scala, including the Cake Pattern. If you Google "Cake Pattern and Scala", you'll get many links, including presentations and videos. For now, here is a link to another question.
Now, as to what is the difference between a self type and extending a trait, that is simple. If you say
B extends A
, thenB
is anA
. When you use self-types,B
requires anA
. There are two specific requirements that are created with self-types:B
is extended, then you're required to mix-in anA
.A
.Consider the following examples:
If
Tweeter
was a subclass ofUser
, there would be no error. In the code above, we required aUser
wheneverTweeter
is used, however aUser
wasn't provided toWrong
, so we got an error. Now, with the code above still in scope, consider:With
Right
, the requirement to mix-in aUser
is satisfied. However, the second requirement mentioned above is not satisfied: the burden of implementingUser
still remains for classes/traits which extendRight
.With
RightAgain
both requirements are satisfied. AUser
and an implementation ofUser
are provided.For more practical use cases, please see the links at the start of this answer! But, hopefully now you get it.
Self types allow you to define cyclical dependencies. For example, you can achieve this:
Inheritance using
extends
does not allow that. Try:In the Odersky book, look at section 33.5 (Creating spreadsheet UI chapter) where it mentions:
Hope this helps.
in the first case, a sub-trait or sub-class of B can be mixed in to whatever uses A. So B can be an abstract trait.
Another thing that has not been mentioned: because self-types aren't part of the hierarchy of the required class they can be excluded from pattern matching, especially when you are exhaustively matching against a sealed hierarchy. This is convenient when you want to model orthogonal behaviors such as:
TL;DR summary of the other answers:
Types you extend are exposed to inherited types, but self-types are not
eg:
class Cow { this: FourStomachs }
allows you to use methods only available to ruminants, such asdigestGrass
. Traits that extend Cow however will have no such privileges. On the other hand,class Cow extends FourStomachs
will exposedigestGrass
to anyone whoextends Cow
.self-types allow cyclical dependencies, extending other types does not