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
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…