4

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?

Dima
  • 39,570
  • 6
  • 44
  • 70
  • http://stackoverflow.com/questions/7694600/behaviour-of-super-in-chained-scala-traits and http://stackoverflow.com/questions/27569901/scala-traits-mixin-order-and-super-call might end up being helpful. – Rob Starling Mar 23 '16 at 16:34
  • I don't see the relation again: the first one is just a confusion about an overriden function call, and the other one is abstract template vs. concrete imlementation ... – Dima Mar 23 '16 at 16:39
  • I think the latter will be a clue, as traits are never concrete. – Rob Starling Mar 23 '16 at 16:46
  • @RobStarling not "traits", _methods_. That question is about differences in handling inheritance with abstract vs. concrete _method_ implementation in the base trait. Interesting read, but different from issue at hand. – Dima Mar 23 '16 at 16:52

2 Answers2

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

jwvh
  • 50,871
  • 7
  • 38
  • 64
  • Doesn't look like it. I added `override def bar = super.bar` to both `One`, and `Two`, so that your "but Two has no bar and One has no bar" is no longer the case, but it still shows the same behavior. – Dima Mar 23 '16 at 17:34
  • @jwvh: That does not really answer the question. If I may, here is how I understand Dima's puzzlement: if the call `super.foo` in `bar` (in the second case) resolves to `Base.foo`, then it seems to indicate that it is resolved statically/lexically, and not dynamically (which by linearization rules would resolve to `Two.foo`). But if that is true, then in the first case we'd similarly expect the call to `super.foo` in `Two.foo` to resolve to `Base.foo`, but here it does not, it **does** resolve to `One.foo`. – Régis Jean-Gilles Mar 23 '16 at 17:35
  • And no, it is not "the same as `new One with Two {} bar`": `new One with Two` don't even have bar ... – Dima Mar 23 '16 at 17:35
  • @RégisJean-Gilles, yes, this is exactly what I am talking about. – Dima Mar 23 '16 at 17:36
  • @RégisJean-Gilles, @Dima `super.foo` in `Two` is resolved to `One.foo` according to the linearization algorithm. What's wrong? In the second case `super` is resolved to `Base.foo` the same way, `foo` without super is called as a virtual method, which resolves to `Two.foo` (head of the linearization chain). – Victor Moroz Jun 25 '16 at 02:08
0

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?

Community
  • 1
  • 1
Rob Starling
  • 3,868
  • 3
  • 23
  • 40
  • 1
    Well, ok, but why doesn't the same reasoning apply to the first example: `Base` is a superclass of `Two`, so why doesn't `super.foo` refer to `Base.foo` there? – Dima Mar 23 '16 at 16:17
  • I also don't see what this has to do with self-types. Can you clarify? – Dima Mar 23 '16 at 16:20
  • re: self-types, `trait One { self: Base => override def foo = "One <: " + super.foo }` is a way of writing a trait `One` that can only be used to extend a class that derives from `Base`. – Rob Starling Mar 23 '16 at 16:23
  • I know :) That's not at all what I was trying to do though. The idea is to be able to combine ("_stack_") several traits extending `Base` into the same class: `class Foo with One with Two with Three with Four ...`, kinda like what I showed in my examples, except, in the first case it works as intended, and in the second case it does not. – Dima Mar 23 '16 at 16:31