I must admit there are still some pretty basic things about Scala that I don't understand. One of the most basic is the mixin behavior. I guess I originally thought the mixin was only applicable for methods not defined in the class that was being mixed in, obviously that's wrong.
Does exactly what I expect and prints: In T.f 1
also does what I expect and prints: In C.f 3
So far so good. Then I try
This is slightly unexpected. I thought i would get: In C.f 3
But it actually prints: In ET.f 2
And then for the totally unexpected:
Which I didn't even know would compile, but when it did i expected: In ET.f In T.f 1
But it actually printed: In ET.f In C.f 3
What are the rules of mixin inheritance in this case?
Some problems are so complex that you have to be highly intelligent and well informed just to be undecided about them. - Laurence J. Peter
The second one is the example that surprised me, but I don't think I'd ever trying mixing in a type that's already a superclass.
Anyway, it surprises me because the mixin declaration is supposed to get priority when method names overlap. I'm wondering if the compiler ignores the "with T" because T is already a parent type.
In the third example, we see this - the ET trait gets priority because it's the furthest to the right in the instantiation.
In the fourth example, the ET trait can only be mixed in with a class that extends T now because ET declared that it extends T. Since C extends T, it's safe to do a mixin of C with ET.
The override abstract/super.f combo works and this kind of strategy is usually meant for stacking abilities. The super call isn't so much a "parent" call like it is in Java, but instead hands off control to the next class or mixin trait in line... the next to the left in declaration of the instantiation. In your example, C with ET, ET's super hands off to C because C is the next to the left in the declaration.
You could try to explore this with: val x = new C with T with ET but it's consistent with example 2, where T will pretty much get ignored and super.f will jump over to C.
But trying: val x = new C with EET with ET with a definition of: gives the result: In EET.f In ET.f In C.f 3
That really baked my noodle. According to the rule... ET.f should have run first. But maybe this is like your second example where the extended trait (in this case ET) gets ignored. But it didn't get ignored completely. Now, does ET.f show up because the EET's super call had ET as the next in priority line... or was this an actual case of super calling the parent (the traditional meaning) rather than passing along to the next in line?
Well, if C is now changed to extend EET, the output only gives the output for C.
Or, if instead I make EET extend T instead of ET trait ET extends T ... trait EET extends T ... class C extends T ... val x = new C with EET with ET... ... I get the output: In ET.f In EET.f In C.f 3
So here's my theory: The rightmost gets priority unless the rightmost already is a parent or ancestor of either the class or another mixed in trait. If that rightmost trait calls super, the trait looks through its parent structure. If its closest parent has the same method, that parent's method is called unless that parent is already a parent of the (leftmost) class. If the trait who called super does not find a match in one of its parents that isn't also a parent of the (leftmost) class, then the "next in line" (next one to the left) gets priority to answer the super call. Repeat as necessary. [ July 13, 2008: Message edited by: Marc Peabody ]
A good workman is known by his tools.
Joined: Jan 17, 2006
Thanks for the explanation Marc, I'll have to mull this all over with a glass of wine to let it sink in (or a nice mixed drink -- if you'll forgive the pun)