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
Related
I found this reading another question:
"[...] a trait that extends a class puts a restriction on what classes can extend that trait - namely, all classes that mix-in that trait must extend that class"
a little example:
class C
trait U
trait T extends C
class D extends U with T // does not work
class E extends T with U // works
Apparently when traits inherit from classes, you have to put the trait in the position that you would otherwise place a class in (i.e. directly after extends)
Now to my questions:
(extended the previous example)
class C
class Test
trait U
trait T extends C
trait Tst extends Test
class D extends U with T // does not work
class E extends T with U // works
class Test2 extends Tst with T
what do we do when we want to inherit form two different traits, where each of those two inherit from a different class? (see class Test 2) this doesn't seem to be possible
if we need to pay attention to the placement of traits that extend a class, how do traits work then? Are traits inheriting from classes not "normal" traits anymore?
This is indeed impossible. You cannot inherit from two different classes at once, whether via traits or otherwise. This is unfortunate, but true. There is no very good reason for that AFAICT, it would be somewhat harder to implement, but not impossible, at least for scala (not "native java") classes. So apparently, it was just decided against at some point, seemingly without a good reason.
Traits that extend classes aren't really "abnormal". It's more like the way scala compiler handles them is. Basically, anything, that is or extends a class has to appear in the extends clause, and not in with. Why? Well, why can you not compare Strings in java with ==? because ...
Here's my guess: class can be defined with parameters, now let's imagine this:
class A(val x: Int)
class B extends A(1)
class C extends A(2)
trait D extends B
trait E extends C
// Oops! x = ? Doesn't compile of course
class F extends D with E
UPDATE:
Disclaimer: I'm not an expert in C++
Here's how C++ is solving the diamond problem:
class A {
int x;
public:
A(int _x) { x = _x; }
int getX() { return x; };
};
class B : virtual public A {
public:
// B b; b.getX == 1
B() : A(1) {}
};
class C : virtual public A {
public:
// C c; c.getX == 2
C() : A(2) {}
};
class D : public B, public C {
public:
// I need to know that B/C inherit A
// A(...) constructors defined above don't apply
D(): B(), C(), A(3) {}
};
I guess that's exactly the problem Scala is trying to avoid.
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.
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 :)
I have a super class:
class P(name:String)
And a helper trait:
trait SysConfig {
def prop(key:String) = System.getProperty(key)
}
Then I want to define an object which extends P:
object C extends P(prop("user.name")
It's not compiled, because it can't find the prop method. So I with the SysConfig:
object C extends P(prop("user.name") with SysConfig
Unfortunately, it still can't be compiled
Is there any way to make it work?
The arg is evaluated in a context outside the current definition, so no.
You have to put the computation in another object.
If you were thinking this, the answer also turns out to be no:
scala> class P(name: String)
defined class P
scala> trait Prop { def prop(k: String) = sys.props(k) }
defined trait Prop
scala> class C(p: String = C.prop("user.name")) extends P(p); object C extends C() with Prop
<console>:9: error: module extending its companion class cannot use default constructor arguments
class C(p: String = C.prop("user.name")) extends P(p); object C extends C() with Prop
^
That's because default args are methods defined by the companion.
Similarly,
scala> class C(p: String) extends P(p); object C extends C(C.prop("user.name")) with Prop
<console>:9: error: super constructor cannot be passed a self reference unless parameter is declared by-name
class C(p: String) extends P(p); object C extends C(C.prop("user.name")) with Prop
^
If I dont misunderstand this :), I think there are 2 things are impossible here.
trait composition or stackable trait is always make right trait wins. In this example, it tries to use left one override the right one.
when we use trait composition, trait structure would not change. The only flexible thing we can do is the sub-trait polymorphism. Cake pattern is using this way, but linearization is a problem. However, it is not related to this.
I think the correct way to do this, is to create a class/trait to do the override thing
class P(name:String)
trait SysConfig {
def prop(key:String) = System.getProperty(key)
}
class C extends P("123") with SysConfig {
override def prop(key: String) = "123"
}
trait Foo extends P with SysConfig {
override def prop(key: String) = "123"
}
new C
Why if I have:
trait T {
def method(a: Int)
}
class A extends T {
//...
}
class B extends A {
//...
}
then when I do this:
//...
val b = new B
b.method(15)
//...
the method() is said to be undefined for B? Why do I have to explicitly say that
class B extends A with T
in order to obtain what I want? Are not traits of parent classes inherited? How can it be so if they may realize a big part of parent's own methods which are inherited by definition? If it is so, what is the argument?
I think you just did not implement the method method because I tested it on my computer and the following code works:
scala> trait T {
| def method(a:Int) =a
| }
defined trait T
scala> class A extends T
defined class A
scala> class B extends A
defined class B
scala> val b = new B
b: B = B#164a40a0
scala> b.method(11)
res25: Int = 11
Your code does not compile, because the method is never implemented. B cannot be instantiated because the classes are all abstract.
Add the method body like this, inside Trait A:
def method(a: Int)={
//do something useful here
}
It then compiles, and there are no errors, and indeed, the instance of B may use the method.