Saturday, April 14, 2018

Traits in Scala

In Scala, traits are a basic mechanism of code reuse. A trait contains fields and methods which can then be reused by mixing them with classes. A class can mix in in any number of traits.

Defining a trait

A trait is defined just like a class, except that it is done with keyword trait.

trait Poet{
  def writePoem(){
    println("Twinkle Twinkle Little Star...")
  }
} 

A trait can declare a superclass,and like any other class default superclass is AnyRef.
A trait can be mixed in to a class by using keyword 'extends' or 'with'.
class BallardArtist extends Poet{
  override def toString = "I write Ballard"}
BallardArtist mixes in Poet by using keyword 'extends'. In this case, class implicitly inherits trait's superclass. So BallardArtist  subclasses AnyRef and mixes in Poet.
Methods inherited by a class from a trait are used just like methods inherited from a superclass.

val ballardArtist = new BallardArtist
ballardArtist.writePoem()
A trait also defines a type.
val poet:Poet = ballardArtist
poet.writePoem()
A class can explicitly extend a superclass with 'extends', and mix in a trait using 'with'.
class BallardArtist extends Person with Poet{
  override def toString = "I write Ballard" 
}
A class can mix in multiple traits using 'with' keyword.
class BallardArtist extends Person with Poet with Artist
 A class can override methods inherited from trait.
class BallardArtist extends Person with Poet with Artist{
  override def toString = "I write Ballard"
  override def writePoem = {
    println("Don't Tell Mama I Was Drinking...")
  }
}
From examples so far, one may incline to infer that Scala traits are similar to Java interfaces with concrete methods. But there is much difference between two. Traits can declare fields and have state.
Anything that can be done in a class can be done in trait too, with two differences.
First, class parameters can't be passed to a trait. Second, 'super' call in case of trait is dynamically bound and not statically bound like in class.

Interface Enrichment

One major benefit of trait is that as it can have concrete methods, it helps in enriching the interface of a class. Unlike interfaces where every implementing class has to provide implementations of the interface methods, trait can provide implementations to be reused by all classes which mix in the trait. With traits, classes need to implement only a small number of abstract methods(thin part of the interface) with trait itself implementing majority of the methods(rich part of the interface).
A very good example of this is Ordered trait in Scala. This trait provides a rich set of comparison methods like <,<=,>,>= etc. If developers want to provide have similar interface for their custom class, they only need to mix in Ordered trait and provide implementation for 'compare' method.Ordered already provides implementation for comparison methods mentioned above in terms of 'compare'.

Stackable behaviour with traits linearization

When we have a class hierarchy and mixed in traits, Scala puts all of them in a linear order. So,if there is a super call in one of them, next method invoked is one up this chain. With this arrangement, if all methods in this call chain call super except last one, we get a stackable behaviour.
In this linearization scheme, a class is always linearized before all of its super classes and mixed in traits.

Suppose, we have following inheritance in our ptogram
class B
trait T1 extends B
trait T2 extends B
trait T3 extends T2
class A extends B with T1 with T3
We can write our linearization scheme as:
L(A )= A+:L(T3)+:L(T1)+:L(B)
In this scheme, :+is sort of concatenation operand such that right operand replaces that part of left operand which it shares.

Linearization of individual participants is as follows:
L(B) = (B, AnyRef, Any)
L(T1) = (T1, B, AnyRef, Any)
L(T2) = (T2, B, AnyRef, Any)
L(T3) = (T3, T2, B, AnyRef, Any)

So,
L(A) = (A)+:(T3, T2, B, AnyRef, Any)+:(T1, B, AnyRef, Any)+:(B, AnyRef, Any)
L(A) = (A,T3, T2,T1, B, AnyRef, Any)+:(T1, B, AnyRef, Any)+:(B, AnyRef, Any)
L(A) = (A,T3, T2,T1, B, AnyRef, Any)+:(B, AnyRef, Any)
L(A) = (A,T3, T2,T1, B, AnyRef, Any)

When any of these classes and traits invokes a method via super, the implementation
invoked will be the first implementation to its right in the linearization.

Example:
abstract class IntQueue{
  def get(): Int
  def put(x: Int)
}
trait Doubling extends IntQueue{
  abstract override def put(x: Int){
  println("In Doubling's put")
  super.put(2*x)
 }
}
trait Incrementing extends IntQueue{
  abstract override def put(x: Int){
  println("In Incrementing's put")
  super.put(x + 1)
 }
}
class BasicIntQueue extends IntQueue{
  private val buf = new ArrayBuffer[Int]
  def get() = buf.remove(0)
  def put(x: Int) {
    println("In BasicIntQueue's put")
    buf += x
  }
}
object StackableTraitsDemo {
  def main(args: Array[String]): Unit = {
    val doublingQueue = new BasicIntQueue with Doubling
    doublingQueue.put(10)
    println(doublingQueue.get())
    val incrThendoublingQueue = new BasicIntQueue with            Doubling with Incrementing
    incrThendoublingQueue.put(10)
    println(incrThendoublingQueue.get())
    val doublingThenIncrQueue = new BasicIntQueue with            Incrementing with Doubling
    doublingThenIncrQueue.put(10)
    println(doublingThenIncrQueue.get())
 }
}
Output is as:
In Doubling's put
In BasicIntQueue's put
20
In Incrementing's put
In Doubling's put
In BasicIntQueue's put
22
In Doubling's put
In Incrementing's put
In BasicIntQueue's put
21






No comments:

Post a Comment