12

Back2Basics: The Story of Trait - Part 4 - Knoldus Blogs

 3 years ago
source link: https://blog.knoldus.com/back2basics-the-story-of-trait-part-4/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Back2Basics: The Story of Trait – Part 4

Reading Time: 4 minutes

In our previous blog The Story of Trait – Part 3, we have discussed the mixin, one of the charming feature of traits. Now we are going to explore chaining of traits.

We have seen how can we mix more than one traits into classes. But what would happen if two traits have same method i.e same name and same signature?

This is where Multiple Inheritance fails. And Traits works really well in such scenario because Traits are Stackable.

What is meant by Stackability?

Suppose we have traits T1, T2 and class A. We have another class B which extends class A and mix traits T1 and T2.

xxxxxxxxxx
class B extends A with T1 with T2

What will happen in this case?

Methods in T1 and T2 instead of colliding with each other they collaborate to form a chain.

An instance of B will chain with an instance of T1 which will chain to an instance of T2. So, when a method is invoked which has the same name in both traits, the first T2 will handle it and T2 could forward it to T1 which after doing some work could forward to an instance of B. And that removes the collision and ends up collaborating nicely.

Let’s take an example from Programming in Scala Book,

We have an abstract class Queue and implemented by a class QueueImplementation.

xxxxxxxxxx
scala> abstract class Queue{
|
| def dequeue(): Option[Int]
|
| def enqueue(x: Int)
|
| }
defined class Queue
scala> class QueueImplementation extends Queue {
|
| private val queue = new scala.collection.mutable.ListBuffer[Int]
|
| def dequeue = if(queue.isEmpty) {
| println("Queue is empty")
| None
| }
| else Some(queue.remove(0))
|
| def enqueue(element: Int) = queue += element
|
| def showQueue = println(s"Queue: $queue")
| }
defined class QueueImplementation
scala> val queue = new QueueImplementation
queue: QueueImplementation = QueueImplementation@4e6ddf37
scala> queue.enqueue(1); queue.enqueue(2); queue.enqueue(3)
scala> queue.showQueue
Queue: ListBuffer(1, 2, 3)
scala> queue.dequeue
res26: Option[Int] = Some(1)
scala> queue.showQueue
Queue: ListBuffer(2, 3)

Well, that works fine. What we need to put the double elements in the queue. We don’t want to change the implementation of Queue. Because Future might demand different implementation like incrementing element. So, We could define traits to provide different modifications like incrementing and doubling the element of Queue. Let’s try creating a trait for double elements.

xxxxxxxxxx
scala> trait Doubling extends Queue {
| abstract override def enqueue(x: Int) = super.enqueue(x * 2)
| }
defined trait Doubling

Here extends does not mean inheriting from Queue. It is used as a constraint here. extends means that trait can only be used or mixin those classes which extend from Queue like QueueImplementation. For more info refer.

Likewise, super here does not mean going to base class in the context of the inheritance hierarchy. But it means going from right to left in chaining of traits. It performs the late binding of that call. And the method is declared abstract override, this is a deadly combination of abstract and override together. By using the keyword override means, we are telling Scala that we are providing an implementation of a known method from the base class. At the same time, we are saying that the actual final implementation of this method will be provided by a class that mixes in the trait.

Let’s define another trait which will increment the element of Queue.

xxxxxxxxxx
scala> trait Incrementing extends Queue {
     | abstract override def enqueue(x: Int) = super.enqueue(x + 1)
     | }
defined trait Incrementing
scala> val queue = new QueueImplementation with Incrementing with Doubling
queue: QueueImplementation with Incrementing with Doubling = $anon$1@35bcb2b4

We have created an object of QueueImplementation with mixing Incrementing and Doubling trait to it. What would be the output if we enqueue any element i.e 4. Will it get doubled or incremented? Will it be 8 or 5?

xxxxxxxxxx
scala> queue.enqueue(4)
scala> queue.showQueue
Queue: ListBuffer(9)

We know traits are stackable, So when we enqueue the element 4, what it did first it went to Doubling, which doubled the elements and then went to Incrementing which increment doubled element by 1 and then went to QueueImplementation which enqueue the element in the queue.

xxxxxxxxxx
scala> val queue = new QueueImplementation with Doubling with Incrementing
queue: QueueImplementation with Doubling with Incrementing = $anon$1@532ef241
scala> queue.enqueue(4)
scala> queue.showQueue
Queue: ListBuffer(10)

Similarly, this time first call went to Incrementing which incremented the value and then went to Doubling to double the value and finally to QueueImplementation class to put the element in the queue.

We have seen how Scala Traits are Stackable. There is no diamond problem in Scala because methods don’t collide instead they start collaborating with each other to form a chaining. The method call is determined by linearization of the classes and traits that are mixed into a class. When we instantiate a class with new, Scala takes the class, and all of its inherited classes and traits, and put them in a linear order. And super call starts calling a method from right to left and net result is the stackable behaviour.

Please feel free to suggest and comment.

References:

knoldus-advt-sticker


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK