Recently, I've find out about the stackable trait pattern and followed the example described here. Everything works, but there is a case I cannot understand :
trait A {
def test : String
}
trait B extends A {
// 'abstract override' modifier required as
// the test() method is not yet implemented
abstract override def test = {
s"B${super.test}"
}
}
class C extends A with B {
// test method concrete implementation
override def test = { "C" }
}
<console>:10: error: overriding method test in trait B of type => String;
method test needs `abstract override' modifiers
class C extends A with B { override def test = { "C" } }
I cannot understand why this does not compile, and why the C::test method needs the mentioned modifier.
I've noticed that there is two modifications I can do in order to make this compile, either by composing the C class at runtime :
class C extends A { override def test = { "C" } }
new C with B // works as expected
or by adding an extra class (which is kind of the same but at compile time):
class C extends A {
override def test = { "C" }
}
class D extends C with B
new D().test
res5: String = BC
Why do I need an extra class (which BTW plays the role of the Basic class) ?
The reason for this behaviour is Scala's class linearization which is used to resolve ambiguities and the semantics of abstract override. But first things first.
Class Linearization
Whenever you have an instance a of type A and you call a method on it a.foobar(), the compiler has to figure out where to find the definition of foobar. Since A can extend any other class and a set of traits, there might be multiple definitions for the function foobar. In order to resolve these ambiguities, Scala will linearize your class A with all its superclasses and traits. The linearization will produce an order in which the different types are checked for a definition of foobar. The first match will be the function which is executed.
The Scala specification defines the linearization as following
Definition 5.1.2 Let C be a class with template C1 with ... with Cn { stats }.
The linearization of C, L(C) is defined as follows:
L(C) = C , L(Cn)+: ... +: L(C1)
Here +: denotes concatenation where elements of the right operand replace identical elements of the left operand.
Since all theory is grey, let's take a look at an example:
trait T1 {
def foobar() = 1
}
trait T2 {
def foobar() = 2
}
class B extends T2 {
override def foobar() = 42
}
class A extends B with T1 with T2 {
override def foobar() = super.foobar()
}
First of all, we have to override the foobar method in the class A, because we have multiple competing definitions for it. However, now is the question, which method definition is called by super.foobar. In order to find this out, we have to calculate the linearization of A.
L(A) = A, L(T2) +: L(T1) +: L(B)
L(B) = B, L(T2)
L(T2) = T2
L(T1) = T1
L(A) = A, T2 +: (T1, B, T2)
L(A) = A, T1, B, T2
Thus, super.foobar will call the definition in T1 which returns 1.
Abstract override
The abstract override modifier for a method basically says that there has to be a class/trait I implementing this method which appears after the trait with the abstract override modifier in the class linearization of your instantiated class. That is necessary in order to execute super.foobar(), because super.foobar() entails that the linearization is further searched for a definition of foobar.
When you now look at your definition of class C then you'll see that it has the following linearization
C, B, A
Consequently, it cannot compile, because beginning from B you don't find an implementation of test.
When we now look at the examples which work, then we'll why they actually work. In the case of C extends A with new C with B, you basically create an anonymous class Z extends C with B. The linearization of Z is
Z, B, C, A
There you see, that B can find in C an implementation of test. Thus, the code can compile. The same holds true for the example with class D.
According to the article you provided:
The base trait (or abstract class) defines an abstract interface that all the cores and stackables extend, as shown in Figure 1. The core traits (or classes) implement the abstract methods defined in the base trait, and provide basic, core functionality. Each stackable overrides one or more of the abstract methods defined in the base trait, using Scala's abstract override modifiers, and provides some behavior and at some point invokes the super implementation of the same method. In this manner, the stackables modify the behavior of whatever core they are mixed into.
In you case:
class C extends A with B { override def test = { "C" } }
you don't have core trait. A is base, as it defines the interface, B is stackable (as it calls super, expecting it to be implemented in core), C is also stackable, as the test declaration in the body of the class is the most concrete (it overrides one from all of the traits).
In your "fixed" examples you just introduced correct core implementation:
class C extends A { override def test = { "C" } }
new C with B // works as expected
class C extends A {
override def test = { "C" }
}
class D extends C with B
Here C defines test before it is overridden by B, so it serves as core.
Related
First of all, I realize that it doesn't make much sense to override a concrete method in superclass by an abstract method in subclass. But... since in Scala it is actually possible to do this, I tried the following snippet, and the result's getting me confused.
First scenario
The concrete method to be overridden in super-super-class
The abstract method is in super-class
class A {
def x: String = "A"
}
abstract class B extends A {
def x: String
}
class C extends B {
def x: String = "C"
}
Executing the snippet above in scala REPL results in the follwing error:
def x: String = "C"
^
<pastie>:10: error: `override` modifier required to override concrete member:
def x: String (defined in class A)
Now the question: Why it seems that the abstract method in class B was ignored? But B.x does actually have an effect if C.x is removed from definition. Because the following snippet doesn't compile either.
class A {
def x: String = "A"
}
abstract class B extends A {
def x: String
}
class C extends B
results in the following error
class C extends B
^
<pastie>:9: error: class C needs to be abstract. No implementation found in a subclass for deferred declaration
def x: String (defined in class B)
Second scenario
The concrete method to be overridden in super-class
The abstract method is in trait
class A {
def x: String = "A"
}
trait B {
def x: String
}
class C extends A with B
Try instantiate C,
scala> (new C).x
val res0: String = A
It looks like the B.x abstract method just got ignored by compiler.
Update
In the first edition of my question, I idiotically forgot to extends A in the second scenario, which leads to an incorrect conclusion that class and trait behave differently in my examples. I sincerely apologize for my negligence.
Let me try to rephase my question:
In both the first and second scenario, what is the effect of the abstract B.x in the middle of class hierarchy?
As I understand it, by inheritance and method resolution order (MRO),
in the first scenario B.x overrides A.x and C.x overrides B.x. Since B.x is abstract, when C.x implements B.x, it need not specify override modifier.
in the second scenario, B.x overrides A.x and C didn't implement the abstract B.x. So C should be abstract and does not compile.
But it seems to me that the compiler just ignored the abstract method B.x in the middle of class hierarchy. Is this behavior defined somewhere in the language specification, or this is totally unintended and unexpected (and just a compiler bug)?
I'm not sure if the question is about the differences between Traits and Abstract Classes or about the differences between extends.
In the First Scenario you have abstract class B extends A in both cases but in the Second Scenario you have trait A and trait B. B is not extending A.
In fact, if you put trait A extends B in the Second Scenario it won't compile
In the second case of the Second Scenario, you are extending 2 different traits that have the same method, having extends A with B your class has the method x defined because the order matters when you extend from multiple traits. If you try to do it in the opposite order extends B with A, it won't compile
In other words:
Second Scenario - First case: Defining an abstract method doesn't need the override
Second Scenario - Second case: C extends A with B is not the same as B extends A + C extends B
Suppose I have next traits:
trait A {
val a: String = "a"
}
trait B {
def a: String = "b"
}
And I want to mix both of these traits into some class C
class C extends B with A
Compiler doesn't allow me to create such class because I have to override method a
I want to override it using for example only A's implementaion. How can I do this?
EDIT
scala> class C extends B with A {
| override val a = super.a
| }
<console>:10: error: super may be not be used on value a
override val a = super.a
^
The compiler can't possibly know which one you intend to use, therefore you must specify it like:
class C extends B with A {
override def a = super[A].a
}
This approach allows you to choose the parent directly, regardless the trait order.
However, the traits define a differently (val and def) thus you must choose only one. You should use either def or val in both traits (not mix them).
Provided you make a a def in the A trait, you can do
class C extends B with A {
override val a = super.a
}
val c = new C
c.a // "a"
This will work because A is extended after B, so super will be its implementation.
Traits in Scala can be used as both mixins and interfaces. It leads to some inconsistence - if I want to close some method inside trait, I just can't do that:
object Library {
protected trait A { def a: Int = 5 }
trait B extends A { private override def a: Int = super.a }
//I want to close `a` memeber for all traits extending B; it's still possible to open it in some another trait `C extends A`, or even `Z extends B with C`
}
// Exiting paste mode, now interpreting.
<console>:10: error: overriding method a in trait A of type => Int;
method a has weaker access privileges; it should not be private
trait B extends A { private override def a: Int = super.a }
^
Such error is totally fine from LSP-perspective as I (or compiler) may want to cast it to the supertype A. But if i'm just using it as mix-in I never need to do that actually, for instance in some Cake-pattern variation. I'll do something like:
import Library._
object O extends B with K with L with App
and that's it. I can't even access trait A here. I know, there is type inference which may go up to super-type, but it's just a "line of types" so compiler could just skip A here and go on (of course it's very very theoretical). Another example - here I had to provide default implementation for method, which I don't really need.
The current solution I use is OOP-composition, but it's not so flexible (as linearization doesn't work here) and not much compatible with mix-ins concept. Some projects I've seen, they actually do mixins and have "over9000" redundand visible members. Several years ago there was an idea to "mark" such mixins composition by with keyword specified instead of extends, but can't even find that thread now.
So, is there any better practices for ad-hoc member encapsulation?
It's not general solution, but it's possible to close methods from the outside world inside one module:
object Library {
protected trait A {
private[Library] def a: Int = 5
private[Library] def b: Int = 7
}
trait B extends A {
def b = super.b
}
}
import Library._
object C extends B
scala> C.a
<console>:179: error: method a in trait B cannot be accessed in object C
C.a
^
scala> C.b
res131: Int = 7
So we're just inverting encapsulation here. If A should be also open for extension:
object Library {
protected trait _A {
private[Library] def a: Int = 5
private[Library] def b: Int = 7
}
trait B extends A { /*...*/ }
trait A extends _A {
override def a = super.a
override def b = super.b
}
}
So, maybe too boilerplate, but at least it works.
P.S. The idea is partially inspired by another deleted answer which didn't work :)
Is there a way to specify that a trait has to provide a concrete implementation of a method?
Given some mixin
class A extends B with C {
foo()
}
The program will compile if either of A, B, or C implements foo(). But how can we force, for example, B to contain foo's implementation?
You can do the following:
class A extends B with C {
super[B].foo()
}
This will only compile if B implements foo. Use with caution though as it (potentially) introduces some unintuitive coupling. Further, if A overrides foo, still B's foo will be called.
One IMHO valid use case is conflict resolution:
trait B { def foo() = println("B") }
trait C { def foo() = println("C") }
class A extends B with C {
override def foo() = super[B].foo()
}
If you want to make sure B declares foo, you can use type ascription:
class A extends B with C {
(this:B).foo()
}
This will only compile if B declares foo (but it might be implemented in C or A).
I have a base class that comes from a Java library, whose code I cannot modify. This class (A) has an empty method (b) which should have been declared as abstract instead:
class A {
def b { }
}
I extend this class in Scala and override the method to make it abstract:
abstract class AA extends A {
override def b
}
Now I implement this method in a trait:
trait B {
def b { println("B") }
}
If I extend AA with trait B, I get a error: overriding method b in class A of type => Unit;
method b in trait B of type => Unit needs `override' modifier:
class C extends AA with B {}
Instead, if the code had been like this, everything is compiled without errors, which seems a bit contradictory to me:
abstract class AA {
def b
}
trait B {
def b { println("B") }
}
class C extends AA with B {}
I'm running Scala 2.8.0RC3, and completely new to the language (3 days). Another weird and related behaviour is that the override label is not necessary when making b abstract:
abstract class AA extends A {
def b
}
To try to see what's going on, I tried this:
scala> class A{
| def b{ }
| }
defined class A
scala> abstract class AA extends A{
| override def b
| }
defined class AA
scala> class AAA extends AA{
| def b = println("AAA")
| }
<console>:8: error: overriding method b in class A of type => Unit;
method b needs `override' modifier
def b = println("AAA")
^
Apparently, the source of the problem is that abstract classes can't "free up" methods in their superclass from the need for the abstract class's subclasses to include the 'override' modifier.
Not sure if this is the right solution, but if your trait B extends A (and override b), then everything compile fine:
First let's define A and AA like you present them in your question:
C:\Users\VonC>scala
Welcome to Scala version 2.8.0.RC5 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_18).
Type in expressions to have them evaluated.
Type :help for more information.
scala> class A {
| def b { println("A b") }
| }
defined class A
scala> new A
res5: A = A#153bedc4
scala> res5.b
A b
scala> abstract class AA extends A {
| override def b
| }
defined class AA
What you did:
scala> trait B {
| override def b { println("B b") }
| }
<console>:6: error: method b overrides nothing
override def b { println("B b") }
^
What I tried with trait B (in order to be able to add the 'override'):
scala> trait B extends A {
| override def b { println("B b") }
| }
defined trait B
So now:
scala> class C extends AA with B {}
defined class C
scala> new C
res7: C = C#1497b7b1
scala> res7.b
B b
The right b overriden method is called with C.b
As for your apparent "inconsistency", see Scala for Java Refugees Part 5: Traits and Types:
To start with, there’s that ever-annoying override keyword. I mentioned back in the article on basic OOP that any method which overrides a method in a superclass must be declared with the override modifier. At the time, I likened it to the language mandating the use of the #Override annotation, with its primary purpose being to enforce the good practice.
The real key to the power of traits is the way in which the compiler treats them in an inheriting class.
Traits are actually mixins, not true parent classes.
Any non-abstract trait members are actually included in the inheriting class, as in physically part of the class. Well, not physically, but you get the picture.
It’s as if the compiler performs a cut-and-paste with the non-abstract members and inserts them into the inheriting class. This means that there’s no ambiguity in the inheritance path, meaning no diamond problem.
So no need for override keyword in your second example.
The problem is very subtle. As a rule of thumb, your class AA, which extends A, should be mixed with traits that also extend A.
You did:
class A {
def b { }
}
abstract class AA extends A {
override def b
}
trait B {
def b { println("B") }
}
Hence when you mix AA and B method b is DEFINED twice. Once by A (Not overrided because the definition in B replaced the override in AA) and the second by B, the compiler can't chose one over the other because there is no hierarchy between the two (equaly named but unrelated) methods. If you want, think about it like this: The compiler "mixes" the bodies of AA and B; if he choses the method from AA it will be abstract, if he choses the method from B (What should happend), since it's not an override, your stuck with two methods b.
To solve this, you want to make sure that both methods override the same method, in which case the the compiler will understand you are talking about the SAME method and will give priority to the last trait mixed.
Now, to override the method b in B, that class has also to inherit from A. So the canonical way to do this would be:
class A {
def b { }
}
abstract class AA extends A {
override def b
}
trait B extends A{
def b { println("B") }
}
class C extends AA with B {}
Which compiles just fine.
Now, when you do:
abstract class AA {
def b
}
trait B {
def b { println("B") }
}
class C extends AA with B {}
it's clear that both methods are the same so the compiler knows he has to use the method from the trait.
Other solutions include:
Make B override AA
Make b in A abstract (But you didn't want that)
Again, the problem is very subtle but I hope I made it a little clearer. To get a better understandig read Scala's Stackable Trait Pattern.