Traits, Stackable Modification and Linearization in Scala

Scala Extractors
Reading Time: 4 minutes

Trait
A trait is similar to a Java interface, but it provides more than those interfaces. A trait can have methods and field definitions, which can then be reused by mixing them into classes.
Let’s start by simply defining a trait.
Defining a trait:

scala> trait Book{
val id: Int
val name: String
}
defined trait Book

In Java, there wasn’t a concept of abstract variables. Only methods and classes can be abstract. Scala has abstract variables. Thus, in the above example, we are able to have id and name without definition.

Mixing abstract and concrete members

Usually in traits we have both concrete and abstract members.

scala> trait Book{
     | val id:Int
     | val name: String
     | val category = "Not finalized yet"
     | val price: Double
     | def getPriceWithTax: Double = {
     | (price * 14)/100
     | }
     | }
defined trait Book

In Java, we use the ‘implement’ keyword to access the members of the interface. In Scala, a trait can be mixed into a class using either the ‘extend‘ or with‘ keywords. Traits can be extended by traits, abstract classes, case classes and by regular classes.

Traits extending traits

scala> trait AlgebraBook extends Book
defined trait AlgebraBook

To implement any member of the trait, use the override modifier.

scala> trait AlgebraBook extends Book{
     | override val category: String = "Algebra Book"
     | }
defined trait AlgebraBook

Regular class, extending traits

For a regular class to extend traits, must define all the abstract members of traits in the class.

scala> class AlgebraBook extends Book{
     | override val id: Int = 101
     | override val name: String = "Algebra Book for Beginners"
     | override val price: Double = 200.50
     | }
defined class AlgebraBook

It’s okay to not define or override the implementation of already defined members in traits. If we want to change the default implementation; as here for getPriceWithTax or category, we can use the override keyword and can provide our own implementation.

with keyword

To mix a trait into a class that explicitly extends a superclass, use extends to indicate the superclass and with to mix in the trait. Let’s make another trait first:

scala> trait Discount{
     | val discount: Int
     | }
defined trait Discount
scala> val algebraBook = new AlgebraBook() with Discount{
     | override val discount: Int = 30
     | }
algebraBook: AlgebraBook with Discount = $anon$1@5891e32e

Stackable Modification

We can mix more than one traits into classes. But if two traits have the same method and signature, how would we resolve this multiple inheritance?
This is where traits play an important role. The stackable modifications state that “super” is accessed dynamically based on how the trait is mixed in, whereas in general super is statically determined.
Traits form a chain in the order they are mixed in. For more on stackable modification, you can visit Stackable Modification With Traits.

class A extends Three with Four with Two {override def print = "In A:" + super.print}
defined class A

Traits offer a form of multiple inheritance. In such cases, the hierarchy is not necessarily linear. It specifies a single linear order for all of the ancestors of a class, including both the regular superclass chain and the parent chains of all of the traits.

We mix in traits as : class A extends B with C with D. The rules for it will follow will be:

  1. Start at the first extended class or trait and write that complete hierarchy down. (linearized hierarchy)
  2. Take the next trait and write this hierarchy down
    • now remove all classes/traits from this hierarchy which are already in the linearized hierarchy
    • add the remaining traits to the bottom of the linearized hierarchy to create the new linearized hierarchy
  3. repeat step 2 for every trait.
  4. Place the class itself as the last type extending the linearized hierarchy
scala> new A().print
res0: String = In A :Four : Two : Three : One

Linearization

Since traits are a way to inherit from multiple classes, the difference lies in the interpretation of super. With multiple inheritance, comes the diamond problem. Linearization solves the diamond problem.
The diamond problem arises ambiguity such that which method to call if both classes have the same method name as well as signature. With traits, the method called is determined by linearization of the classes and traits that are mixed into a class.
In our example, linearization took place in the following manner:

Linearization happens from back to front. In this case
1. First, three will be linearized, which looks like:
Three -> One -> AnyRef -> Any
2. Then Four is linearized:
Four-> Two -> One -> AnyRef -> Any
3. Next is Two. Its Linearization will be:
Two-> One-> AnyRef-> Any

Linearization-1
Linearization – Diagram 1

Because Two-> One-> AnyRef-> Any has already appeared. It can be ignored in step 3.

Linearization-2
Linearization – Diagram 2

This leaves us with
Three -> One -> AnyRef -> Any
Four-> Two -> One -> AnyRef -> Any

Now, One -> AnyRef -> Any has already appeared. It can be ignored.

Linearization-3
Linearization – Diagram 3


So, the remaining hierarchy will be: Four-> Two-> Three-> One-> AnyRef-> Any.

Linearization-4
Linearization – Diagram 4

4. Finally, A will be added and final linearization order will be:
A-> Four-> Two-> Three-> One-> AnyRef-> Any

Linearization-5
Linearization – Diagram 5

Hence, result is:
res0: String = In A :Four : Two : Three : One

Conclusion

Traits are more related to abstract classes than to interfaces. The main difference is that trait does not have a constructor. Whenever you need to have a constructor for your OOP logic, then an abstract class will suit better, for all else traits are much better.

Trait-linearization
Scala By Example

Knoldus Pune Careers - Hiring Freshers

Get a head start on your career at Knoldus. Join us!