I know this question is old but I would like to add some clarifications 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 it 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 it 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 it 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 isQuiet: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, whoever 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