Trait 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. linearization helps to resolve the diamond problem which occurs when a class or trait inherits a same property from 2 different concrete classes or traits.
Syntax :
trait C{} trait B{} class A{} object a_obj= new class A extends B with C
The linearization will look like as follows :-
C-> AnyRef-> Any B-> AnyRef-> Any A-> AnyRef-> Any a_obj-> A-> C-> B-> AnyRef-> Any
Here Any is the superclass of all classes, also called the top class. It defines certain universal methods such as equals, hashCode, and toString. AnyRef represents reference classes. All non-value types are defined as reference types. AnyRef corresponds to java.lang.object . Every Scala trait and class implicitly extend these Scala objects at the end of linearization hierarchy.
Examples :
// Scala program defining trait A trait A { def name : String } // defining trait B inheriting A trait B extends A { override def name : String = "class b" } // defining trait C inheriting A trait C extends A { override def name : String = "class c" } // defining class D inheriting B and C both class D extends B with C { override def name : String = super .name } // Creating object object GFG { // Main method def main(args : Array[String]) { var class _ d = new D // whose property will be inherited println(class _ d.name) } } |
Output :
class c
Linearization for class D follows dark bold arrow. Inheritance for class D follows light arrow.
As we can see in above diagram linearization will not be as same as inherited structure. Scala traits/classes are dynamically placed in linear order in which will the linearization will be applied as below.
D-> C-> B-> A-> AnyRef-> Any
Following rules are followed for the determining the linearization:
- Take the first extended trait/class and write its complete inherited hierarchy in vertical form, store this hierarchy as X.
- Take the next trait/class after the with clause, write its complete hierarchy and cancel the classes or traits that are repeated in hierarchy X. Add the remaining traits/classes to the front of the hierarchy X.
- Go to step 2 and repeat the process, until no trait/class is left out.
- Place the class itself in front of hierarchy as head for which the hierarchy is being written.
Let’s understand some examples.
Example :
// Scala program for linearization // defining old_car class class old _ Car { def method : String = "old car " } // defining new_Car_Designs trait trait new _ Car _ Designs extends old _ Car { override def method : String = "Designing-> " + super .method } // defining new_Car_Part trait trait new _ Car _ Part extends old _ Car { override def method : String = "Add new part-> " + super .method } // defining new_Car_Paint trait trait new _ Car _ Paint extends old _ Car { override def method : String = "Repainting-> " + super .method } // defining new_Car class class new _ Car extends new _ Car _ Paint with new _ Car _ Part with new _ Car _ Designs { override def method : String = "new car-> " + super .method } // Creating object object geekforgeeks { // Main method def main(args : Array[String]) { // new_Car object var car 1 = new new _ Car println(car 1 .method) } } |
Output :
new car-> Designing-> Add new part-> Repainting-> old car
Example :
// Scala program for trait linearization // defining classes and traits class flavour { def make (flavour : String) : Unit = { println(flavour) } } // defining texture trait trait texture extends flavour { abstract override def make (flavour : String) { super .make(flavour + "texture " ) } } // defining cream trait trait cream extends texture { abstract override def make (flavour : String) { super .make(flavour + "with cream " ) } } // defining jelly trait trait jelly extends texture { abstract override def make (flavour : String) { super .make(flavour + "with jelly " ) } } // defining cone trait trait cone extends flavour { abstract override def make (flavour : String) { super .make(flavour + "in cone " ) } } // creating new ice-cream flovours // with above traits and classes // inheriting different traits and classes class Myflavour extends flavour with jelly { override def make (flavour : String) { super .make(flavour) } } class Myflavour 2 extends flavour with cream with cone { override def make (flavour : String) { super .make(flavour) } } // Creating object object GFG { // Main method def main(args : Array[String]) { // creating new objects var icecream 1 = new Myflavour var icecream 2 = new Myflavour 2 with jelly println(icecream 1 .make( "chocolate " )) println(icecream 2 .make( "vanilla " )) } } |
Output :
chocolate with jelly texture () vanilla with jelly in cone with cream texture ()
Important Points About Linearization
- Scala solves ambiguity of traits/classes by linearization process.
- Scala uses linearization whenever a new class has been instantiated. Taking all the traits/classes and forming a linear order which points to corresponding super classes/traits thus super method knows its parent method.
- These super method calling is done in a stackable manner.
- Linearization may or not be the same as the inherited mixins as they are written.
- We cannot explicitly add a class to inheritance when it is already been implicitly inherited in a linearization otherwise it will result in error as inheritance twice.
- No trait/class is ever repeated in linearization.