Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Linearization order in Scala

Tags:

scala

traits

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

like image 555
woodtluk Avatar asked Dec 12 '15 17:12

woodtluk


People also ask

What is linearization in Scala?

Scala Linearization is a deterministic process which comes into play when an object of a class is created which is defined using inheritance of different traits and classes.

How Scala resolves diamond problem?

Scala resolves the diamond problem by defining one main super trait, whose code will be used, among all super traits. The main one is set with the extends keyword, while the others are set with with . Hence, in the above example, WindowDoor. open() will, by default, use code from Door.


2 Answers

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 
like image 101
metaphori Avatar answered Sep 20 '22 03:09

metaphori


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:

enter image description here

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} 
like image 28
Mehran Avatar answered Sep 21 '22 03:09

Mehran