I have difficulties in understanding the linearization order in Scala when working with traits:
class A {
def foo() = "A"
}
trait B extends A {
override def foo() = "B" + super.foo()
}
trait C extends B {
override def foo() = "C" + super.foo()
}
trait D extends A {
override def foo() = "D" + super.foo()
}
object LinearizationPlayground {
def main(args: Array[String]) {
var d = new A with D with C with B;
println(d.foo) // CBDA????
}
}
It prints CBDA but I can't figure out why. How is the order of the traits determined?
Thx
An intuitive way to reason about linearisation is to refer to the construction order and to visualise the linear hierarchy.
You could think this way. The base class is constructed first; but before being able of constructing the base class, its superclasses/traits must be constructed first (this means construction starts at the top of the hierarchy). For each class in the hierarchy, mixed-in traits are constructed left-to-right because a trait on the right is added "later" and thus has the chance to "override" the previous traits. However, similarly to classes, in order to construct a trait, its base traits must be constructed first (obvious); and, quite reasonably, if a trait has already been constructed (anywhere in the hierarchy), it is not reconstructed again. Now, the construction order is the reverse of the linearisation. Think of "base" traits/classes as higher in the linear hierarchy, and traits lower in the hierarchy as closer to the class/object which is the subject of the linearisation.
The linearisation affects how `super´ is resolved in a trait: it will resolve to the closest base trait (higher in the hierarchy).
Thus:
var d = new A with D with C with B;
Linearisation of A with D with C with B is
(top of hierarchy) A (constructed first as base class)
linearisation of D
A (not considered as A occurs before)
D (D extends A)
linearisation of C
A (not considered as A occurs before)
B (B extends A)
C (C extends B)
linearisation of B
A (not considered as A occurs before)
B (not considered as B occurs before)
So the linearization is: A-D-B-C.
You could think of it as a linear hierarchy where A is the root (highest) and is constructed first, and C is the leaf (lowest) and constructed last. As C is constructed last, it means that may override "previous" members.
Given these intuitive rules, d.foo calls C.foo, which returns a "C" followed by super.foo() which is resolved on B (the trait on the left of B, i.e. higher/before, in the linearization), which returns a "B" followed by super.foo() which is resolved on D, which returns a "D" followed by super.foo() which is resolved on A, which finally returns "A". So you have "CBDA".
As another example, I prepared the following one:
class X { print("X") }
class A extends X { print("A") }
trait H { print("H") }
trait S extends H { print("S") }
trait R { print("R") }
trait T extends R with H { print("T") }
class B extends A with T with S { print("B") }
new B // X A R H T S B (the prints follow the construction order)
// Linearization is the reverse of the construction order.
// Note: the rightmost "H" wins (traits are not re-constructed)
// lin(B) = B >> lin(S) >> lin(T) >> lin(A)
// = B >> (S >> H) >> (T >> H >> R) >> (A >> X)
// = B >> S >> T >> H >> R >> A >> X
The accepted answer is wonderful, however, for the sake of simplification, I would like to do my best to describe it, in a different way. Hope can help some people.
When you encounter a linearization problem, the first step is to draw the hierarchy tree of the classes and traits. For this specific example, the hierarchy tree would be something like this:
The second step is to write down all the linearization of the traits and classes that interferes the target problem. You will need them all in the one before the last step. For this, you need to write just the path to reach the root. The Linearization of traits are as following:
L(A) = A
L(C) = C -> B -> A
L(B) = B -> A
L(D) = D -> A
The third step is to write the linearization of the problem. In this specific problem, we are planning to solve the linearization of
var d = new A with D with C with B;
Important note is that there is a rule by which it resolves method invocation by first using right-first, depth-first search. In another word, you should start writing the Linearization from most right side. It is as follow:
L(B)>>L(C)>>L(D)>>L(A)
Fourth step is the most simple step. Just substitute each linearization from second step to third step. After substitution, you will have something like this:
B -> A -> C -> B -> A -> D -> A -> A
Last but not least, you should now remove all duplicated classes from left to right. The bold chars should be removed:
B -> A -> C -> B -> A -> D -> A -> A
You see, you have the result: C -> B -> D -> A
Therefore the answer is CBDA.
I know it is not individually deep conceptual description, but can help as an complementary for the conceptual description I guess.
And this part explains by relying on formula:
Lin(new A with D with C with B) = {A, Lin(B), Lin(C), Lin(D)}
Lin(new A with D with C with B) = {A, Lin(B), Lin(C), {D, Lin(A)}}
Lin(new A with D with C with B) = {A, Lin(B), Lin(C), {D, A}}
Lin(new A with D with C with B) = {A, Lin(B), {C, Lin(B)}, {D, A}}
Lin(new A with D with C with B) = {A, Lin(B), {C, {B, Lin(A)}}, {D, A}}
Lin(new A with D with C with B) = {A, Lin(B), {C, {B, A}}, {D, A}}
Lin(new A with D with C with B) = {A, {B, A}, {C, {B, A}}, {D, A}}
Lin(new A with D with C with B) = {C,B,D,A}
Scala's traits stack, so you can look at them by adding them one at a time:
Start with new A => foo = "A"
Stack with D => foo = "DA"
Stack with C which stacks with B => foo = "CBDA"
Stack with B does nothing because B is already stacked in C => foo = "CBDA"
Here's a blog post about how Scala solves the diamond inheritance problem.
The process by which scala resolve the super call is called Linearization
In your example you create Object as
var d = new A with D with C with B;
So as specified scala reference docs Here call to super will be resolved as
l(A) = A >> l(B) >> l(c) >> l(D)
l(A) = A >> B >> l(A) >> l(C) >> l(D)
l(A) = A >> B >> A >> C >> l(B) >> l(D)
l(A) = A >> B >> A >> C >> B >> l(A) >> l(D)
l(A) = A >> B >> A >> C >> B >> A >> l(D)
l(A) = A >> B >> A >> C >> B >> A >> D >> l(A)
l(A) = A >> B >> A >> C >> B >> A >> D >> A
Now Start from left and remove duplicate construct in which right will be winning one
e.g. remove A and we get
l(A) = B >> C >> B >> D >> A
remove B and we get
l(A) = C >> B >> D >> A
Here we don't have any duplicate entry
Now starting calling from C
C B D A
super.foo in class C will call foo in B and foo in B call foo in D and so on.
P.S. here l(A) is linearization of A
Well Actually I see you just reversed the Constructor linearization, which I think is pretty simple, so First let's understand constructor linearization
First Example
object Linearization3 {
def main(args: Array[String]) {
var x = new X
println()
println(x.foo)
}
}
class A {
print("A")
def foo() = "A"
}
trait B extends A {
print("B")
override def foo() = super.foo() + "B" // Hence I flipped yours to give exact output as constructor
}
trait C extends B {
print("C")
override def foo() = super.foo() + "C"
}
trait D extends A {
print("D")
override def foo() = super.foo() + "D"
}
class X extends A with D with C with B
Which outputs:
ADBC
ADBC
So to calculate the output I just take the class/traits one by one from left to right then recursively write outputs (without duplicates) here is how:
Our class signature is : class X extends A with D with C with B
So the first is A, since A has no parents (deadend) just print its constructor
Now D, which extends A, since we already printed A, then let's print D
Now C, which extends B, which extends A, so we skip A because it's already have been printed, we then print B , then print C (it's like a recursive funtion)
Now B, which extends A, we skip A, and we also skip B (nothing printed)
and you got ADBC !
Reversed Example (Your example)
object Linearization3 {
def main(args: Array[String]) {
var x = new X
println()
println(x.foo)
}
}
class A {
print("A")
def foo() = "A"
}
trait B extends A {
print("B")
override def foo() = "B" + super.foo()
}
trait C extends B {
print("C")
override def foo() = "C" + super.foo()
}
trait D extends A {
print("D")
override def foo() = "D" + super.foo()
}
class X extends A with D with C with B
The Output is:
ADBC
CBDA
I hope that was simple enough for beginners like me
In addition to other anwsers you can find step-by-step explanation in snippet result below
hljs.initHighlightingOnLoad();
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.0.0/highlight.min.js"></script>
<link href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.0.0/styles/zenburn.min.css" rel="stylesheet" />
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" />
<table class="table">
<tr>
<th>Expression</th>
<th>type</th>
<th><code>foo()</code> result</th>
</tr>
<tr>
<td><pre><code class="scala"> new A </code></pre>
</td>
<td><pre><code class="scala"> A </code></pre>
</td>
<td><pre><code class="scala">"A"</code></pre>
</td>
</tr>
<tr>
<td><pre><code class="scala"> new A with D </code></pre>
</td>
<td><pre><code class="scala"> D </code></pre>
</td>
<td><pre><code class="scala">"DA"</code></pre>
</td>
</tr>
<tr>
<td><pre><code class="scala"> new A with D with C </code></pre>
</td>
<td><pre><code class="scala"> D with C </code></pre>
</td>
<td><pre><code class="scala">"CBDA"</code></pre>
</td>
</tr>
<tr>
<td><pre><code class="scala"> new A with D with C with B </code></pre>
</td>
<td><pre><code class="scala"> D with C </code></pre>
</td>
<td><pre><code class="scala">"CBDA"</code></pre>
</td>
</tr>
</table>
explanation, how the compiler sees a class Combined which extends the traits A with D with C with B
class Combined extends A with D with C with B {
final <superaccessor> <artifact> def super$foo(): String = B$class.foo(Combined.this);
override def foo(): String = C$class.foo(Combined.this);
final <superaccessor> <artifact> def super$foo(): String = D$class.foo(Combined.this);
final <superaccessor> <artifact> def super$foo(): String = Combined.super.foo();
def <init>(): Combined = {
Combined.super.<init>();
D$class./*D$class*/$init$(Combined.this);
B$class./*B$class*/$init$(Combined.this);
C$class./*C$class*/$init$(Combined.this);
()
}
};
reduced example
You can read from left to right. Here is a small example. The three traits will print their name when initialized i.e. extended:
scala> trait A {println("A")}
scala> trait B {println("B")}
scala> trait C {println("C")}
scala> new A with B with C
A
B
C
res0: A with B with C = $anon$1#5e025e70
scala> new A with C with B
A
C
B
res1: A with C with B = $anon$1#2ed94a8b
So this is the basic linearization order. So the last one will overwrite the previous one.
Your problem is a little more complex. As you traits already extend other traits that themselves override some values of the previous traits.
But the initialization order left to right or right will override left.
You have to keep in mind that the trait itself will be initialized first.
Related
Here, following is explained
trait A { def common = “A” }
trait B extends A { override def common = “B” }
trait C extends A { override def common = “C” }
class D1 extends B with C
class D2 extends C with B
In case of (D1), the superclass of C is B.
Following same reasoning, in case of (D2), the superclass of B is C.
So is it possible to vary hierarchy relationships dynamically by varying traits linearly?
Also asked here:
https://users.scala-lang.org/t/type-linearization/2533
This is a bit big for a comment, so:
In both cases (D1 and D2) B extends A and C also extends A. B and C don't become superclasses of each other, it's just the matter of how Scala resolves the diamond problem.
Think of type linearization as of "if you mix in 2 traits that extend from a common parent and thus implement a method with the same signature, the rightmost one wins". This means that in your examples:
class D1 extends B with C
B and C both implement A, but C is the rightmost in D1 definition. This means C.common will be called if you call common on D1. And in the next example:
class D2 extends C with B
the story is just the same, but B is the rightmost in its definition, thus its implementation will be called on D2.common
In following code, why does method A1 of traits C and D are called even though class B has defined A1?
scala> trait A {
| def A1 = A2
| def A2 //A2 is abstract and need implementation
| }
defined trait A
scala> class B extends A {
| def A2 = println("B") //implemented A2 in B
| }
defined class B
scala> val b1 = new B
b1: B = B#163e8949
this works fine.
scala> b1.A1
B
scala> b1.A2
B
now I mix traits. C still has A2 abstract
scala> trait C extends A {
| abstract override def A1 = {super.A1; C}
| def C = println("C")
| }
defined trait C
D still has A2 abstract strong text
scala> trait D extends A {
| abstract override def A1 = {super.A1; D}
| def D = println("D")
| }
defined trait D
Why C and D are printed when I cam calling A1 of B. Shouldn't B's A1 only call B's A2 which is implemented to print B?
scala> val b2 = new B with C with D
b2: B with C with D = $anon$1#701d2b59
scala> b2.A1
B
C
D
scala> b2.A2
B
scala> val b3 = new B with D with C
b3: B with D with C = $anon$1#270f28cf
scala> b3.A1
B
D
C
I suppose what I am using is called stackable trait pattern. Basically, if I use new B with C with D and call a method, compiler will see if the method is defined in D, then C and then B (order is right to left). When the compiler sees an implementation it uses it. The use of super makes the compiler call the next implementation available in the class or trait on left (again right to left order)
Removed super calls.
scala> trait D extends A {
| abstract override def A1 = { D}
| def D = println("D")
| }
defined trait D
scala> trait C extends A {
| abstract override def A1 = { C }
| def C = println("C")
| }
defined trait C
scala> val b3 = new B with D with C
b3: B with D with C = $anon$1#7417ef4f
As super is removed, when compiler sees call to A1, it checks C first (rightmost, order is right to left) and it finds A1 and uses that implementation. As A1 no longer uses super, A1 in D or B are not called.
scala> b3.A1
C
If D is rightmost then only D's A1 is called
scala> val b3 = new B with C with D
b3: B with C with D = $anon$1#fd4459b
scala> b3.A1
D
Added super after print. This changes order or printing.
scala> trait C extends A {
| abstract override def A1 = { C ; super.A1}
| def C = println("C")
| }
defined trait C
scala> val b3 = new B with C with D
b3: B with C with D = $anon$1#4489f60f
scala> b3.A1
D
As super is called, after calling A1 from D, compiler looks for A1 of super class (next one on left) and keeps going towards left.
scala> val b3 = new B with D with C
b3: B with D with C = $anon$1#7b3c0ecb
scala> b3.A1
C
D
scala>
Why C and D are printed when I cam calling A1 of B. Shouldn't B's A1 only call B's A2 which is implemented to print B?
No. super.A1 refers to A#A1, which calls A2 of the object it's called on: not B#A2, not A#A2, but this.A2.
Also, C and D aren't related to B in any way, so they couldn't be calling B#A1 in any case: did you mean class C/D extends B?
class Cell(var x: Int)
var c = new Cell(1)
val f1 = () => c.x /* Create a closure that uses c */
def foo(e: Cell) = () => e.x /* foo is a closure generator with its own scope */
// f2 wont do any reference/deep copy
val f2 = foo(c) /* Create another closure that uses c */
val d = c /* Alias c as d */
c = new Cell(10) /* Let c point to a new object */
d.x = d.x + 1 /* Increase d.x (i.e., the former c.x) */
// now c.x refers to 10
println(f1()) /* Prints 10 */
println(f2()) /* Prints 2 */
Here the f2() prints 2 , As scala wont do deep copy, why the value is still persisted as 1, it should be 10.. where i am going wrong
2) I had read smomehere, Closure in scala dont deep copy the objects, they just keep reference to the object. what do it exactly mean
Your example is somewhat tough to understand due to the way you copied it in (it looks like all the code is run when a Cell is created, but you'd get infinite recursion if that were true). The reason f1 and f2 return different results is that they are pointing at different Cells. You are right that when you write:
val d = c
both c and d contain the same reference. But when you write:
c = new Cell(10)
c is now a reference to a new cell, and d won't copy over that reference.
It's easier to see this with REPL, which can print hexadecimal reference locations.
scala> class Cell(var x: Int)
defined class Cell
scala> var a = new Cell(5)
a: Cell = Cell#368239c8
scala> val b = a
b: Cell = Cell#368239c8
We can see that a and b contain references to the same cell.
scala> a.x = 10
a.x: Int = 10
scala> b.x
res0: Int = 10
When we update the class referenced by a, it also updates for b.
scala> a = new Cell(7)
a: Cell = Cell#5b87ed94
scala> b
res1: Cell = Cell#368239c8
scala> a.x
res2: Int = 7
scala> b.x
res3: Int = 10
When we assign our variable a to a new cell, it has a different reference location (it is a different instance of Cell). b still has the same reference (why wouldn't it?).
Scala code:
trait X
trait Y1 {
this: X =>
}
trait Y2 extends X {
}
I wonder what's the difference between Y1 and Y2 traits? If I have a class Z need to extend Y:
class Z1 extend Y1 with X
class Z2 extends Y2
It seems there is no difference for the two classes Z1 and Z2. If they are nearly the same, which style would you recommend?
Sometimes, you're in a deeply nested scope, and the alias is useful for the outer object, for instance here.
As explained here, the linearizations of the two types for this are inverted.
That is, you have this: Y with X and this: X with Y.
this matters (pun intended):
scala> trait X { type T }
defined trait X
scala> trait Y { type T <: String ; def t: T ; def f = t.length }
defined trait Y
scala> trait Y { this: X => type T <: String ; def t: T ; def f = t.length }
<console>:8: error: value length is not a member of Y.this.T
trait Y { this: X => type T <: String ; def t: T ; def f = t.length }
^
scala> trait Y { this: X with Y => type T <: String ; def t: T ; def f = t.length }
defined trait Y
scala> trait Y extends X { type T <: String ; def t: T ; def f = t.length }
defined trait Y
But as the linked, authoritative answer says, it may not matter forever.
trait B extends A means that B is an A.
trait B { this: A => } is dependency injection - you want B to require A.
Consider these two REPL sessions:
> trait A
defined trait A
> trait B extends A
defined trait B
> class C
defined class C
> new C with A
res0: C with A = $anon$1#706abf59
> new C with B
res1: C with B = $anon$1#2004aa19
And this:
> trait A
defined trait A
> trait B { this : A => }
defined trait B
> class C
defined class C
> new C with A
res0: C with A = $anon$1#1ef535db
> new C with B
<console>:11: error: illegal inheritance;
self-type C with B does not conform to B's selftype B with A
new C with B
^
I would like to have a class B which can be extended with two field by two traits A_1 and A_2.
It is important, that class B does not have these two fields by itself, since class B should also be able to be used without these to fields.
My first idea:
trait A_1 { val x: Int }
trait A_2 { val y: Int }
class B
But new B with A_1 with A_2 does not work, since x and y are abstract members and need to be defined in class B.
My second idea:
trait A_1 { var x: Int = _}
trait A_2 { var y: Int = _}
class B
Then one could set the fields after creating a object of B:
val b = new B with A_1 with A_2
b.x = 1
b.y = 2
This works, but is a bit ugly, since the values have to be set afterwards. This construction also makes the class mutable.
My third idea:
class A_1 (val x: Int)
class A_2 (val y: Int)
But class B is not allowed to extend multiple classes, so this idea does not work.
Which possibilites are left to realize this situation ?
Is there a better way than idea 2. ?
You were so close:
scala> trait A1 {val x: Int}
defined trait A1
scala> trait A2 {val y: Int}
defined trait A2
scala> class B
defined class B
scala> new B with A1 with A2 {val x = 4; val y = 2}
res0: B with A1 with A2 = $anon$1#2bc20611
What Nicolas suggested.
Or:
scala> trait A_1 { val x: Int }
trait A_2 { val y: Int }
class B
defined trait A_1
defined trait A_2
defined class B
scala> new { val x = 3; val y = 9 } with B with A_1 with A_2
res3: B with A_1 with A_2 = $anon$1#18e1b
The feature used here is known as early initialization.