I can't come up with a very good description of this in words, so, please take a look at this example:
trait Base { def foo = "Base" }
trait One extends Base { override def foo = "One <: " + super.foo }
trait Two extends Base { override def foo = "Two <: " + super.foo }
new Base with One with Two {} foo
This prints: Two <: One <: Base
, which is what I expect.
Now, I am trying to add another level, so that overriding traits would not have to call super
explicitly. Like this:
trait Base { def foo = "Base" }
trait Foo extends Base { def bar = foo + " <: " + super.foo }
trait One extends Foo { override def foo = "One" }
trait Two extends Foo { override def foo = "Two" }
new Foo with One with Two {} bar
Here, the last line prints out Two <: Base
So, it looks like in the first example super
means One
, while in the last one it skips One
and goes directly to Base
.
Why is this happening? Shouldn't behavior be the same?
In the 1st case, new Base with One with Two {} foo
(which is the same as new One with Two {} foo
), the "trait stack" is pretty obvious. The Two
has a foo
which calls the foo
of its super (One
) which calls the foo
of its super (Base
).
In the 2nd case, new Foo with One with Two {} bar
(which is the same as new One with Two {} bar
), the "trait stack" is Base->Foo->One->Two. You call bar
but Two
has no bar
and One
has no bar
. Foo
has a bar
that calls the foo
of its super (Base
).
UPDATE
Consider this mod as @Dima has proposed.
trait Base { def foo = "Base" }
trait Foo extends Base { def bar = foo + " <: " + super.foo }
trait One extends Foo { override def bar = super.bar
override def foo = "One" }
trait Two extends Foo { override def bar = super.bar
override def foo = "Two" }
new One with Two {} bar // no Base or Foo needed
Yes, this gives the same output as before: res0: String = Two <: Base
Now Two
calls the bar
of its super(One
) which calls the bar
of its super (Foo
) which calls the foo
(not bar
) of its super.
All this bar
activity is separate from the foo
definitions. Two
never invokes the foo
of its super so the One.foo
is never used and cannot be a part of the output.
A DIFFERENT APPROACH
Consider the following.
trait B { def id = "B" } // B for Base
trait V extends B { override def id = "V" }
trait W extends B { override def id = "W" }
trait X extends B { override def id = "X" }
trait Y extends B { override def id = "Y" }
trait Z extends B { override def id = "Z" }
trait R extends B { override def id = "R"; def mySup = super.id } // Required
Now try instantiating this in multiple different ways.
val r = new V with Y with W with R with X {} // B not needed
// or
val r = new W with R with Z with X with V {}
// or
val r = new R with Y with V with B with W {}
// or
val r = new Z with Y with X with W with R {}
// etc.
In each case r.id
will be the last trait in the chain and r.mySup
will be the trait that comes before the R
(or B
if nothing is specified before the R
).
super.foo
in the Foo
definition means Base.foo
as that's the superclass of Foo
, where the definition is. super
is not a magical keyword that means "one superclass up from the final class".
Incidentally, you might also want to look into self-types. See What is the difference between self-types and trait subclasses?