I know this question is old but I would like to add some clarification and examples.
There are three main differences between trait inheritance and self types.
Semantics
Inheritance is one of the relationships with the most coupling of the object paradigm, if A extends B, that means that A is a B.
Let's say we have the following code,
trait Animal {
def stop():Unit = println("stop moving")
}
class Dog extends Animal {
def bark:String = "Woof!"
}
val goodboy:Dog = new Dog
goodboy.bark
// Woof!
We are saying that a Dog is an Animal. We can send the messages bark
and stop
to goodboy
because is a Dog, it understand both methods.
Now suppose we have a new trait,
trait Security {
this: Animal =>
def lookout:Unit = { stop(); println("looking out!") }
}
This time Security is NOT an Animal, and that is fine because would be semantically incorrect if we affirm that a Security is an Animal, they are different concepts, that can be used together.
So now we can create a new kind of dog,
val guardDog = new Dog with Security
guardDog.lookout
// stop moving
// looking out!
guardDog
is a Dog, an Animal and Security. It understand stop
, bark
and lookout
because is a Dog with Security.
But what happens if we create a new dog like this?
val guardDog2:Dog = new Dog with Security
guardDog2.lookout // no such method!
guardDog2
is just a Dog, so we can't call lookout
method. (okok, it's a Dog with Security, but we just see a Dog)
Cyclic Dependencies
Self Types allow us to create cyclic dependencies between types.
trait Patient {
this: Reader =>
def isQuite:Boolean = isReading
def isSlow:Boolean = true
}
trait Reader {
this: Patient =>
def read():Unit = if(isSlow) println("Reading Slow...") else println("Reading Fast...")
def isReading = true
}
val person = new Patient with Reader
The following code doesn't compile.
trait Patient extends Reader { /** code **/}
trait Reader extends Patient { /** code **/ }
This kind of code is very common in dependency injection (cake pattern).
Versatility
Last but not least, who uses our traits can decide the order in which they are used, so thanks to Trait Linearization the final result can be different although the traits used are the same.
With normal inheritance we can't do that, the relations between traits and classes are fixed.
trait Human {
def isGoodForSports:Boolean
}
trait Programmer extends Human {
def readStackOverflow():Unit = println("Reading...")
override def isGoodForSports: Boolean = false
}
trait Sportsman extends Human {
def play():Unit = println("Playing something")
override def isGoodForSports: Boolean = true
}
val foo = new Programmer with Sportsman
foo.isGoodForSports
// true
val bar = new Sportsman with Programmer
bar.isGoodForSports
// false
Hope this can be useful.