Meaning of super in stacked traits depends on call

2019-06-28 07:12发布

问题:

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?

回答1:

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).



回答2:

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?