Scala Nuggets: Understanding Traits

Posted on Tuesday, May 24, 2011

1


Inphina provides specialized Scala consulting, training and offshoring … Learn more about Scala@Inphina!

If you are coming from a Java background, then traits are interfaces++.  In scala, trait is a complete mixin solution where we can provide implementation in the trait as well as have classes mix-in the trait either when we declare classes or even when we are creating an instance of the class.

Hence, both of these are valid if Metering was a trait, which can do any kind of counting

class Tablet extends Device with Metering { …

val anotherPhone = new Phone with Metering { …

This is powerful where we can give a trait to a class at the time of instantiation.

Let us consider our business scenario where we want any new device to be metered. Let us say that there are two devices for now a Tablet and a Phone which have the following definitions

abstract class Device {
  def setPrice(x: Double)
}

class Phone extends Device {
  def setPrice(x: Double) = {
    println("the cost for the phone is " + x)
  }
}

class Tablet extends Device with Metering {
  def setPrice(x: Double): Unit = {
    println("Price for the tablet is " + x)
    addObserver(DeviceCounter)
    notifyObservers
  }
}

As you would notice, the Phone class does not mixin the trait Metering, whereas the Tablet class does. Let us look at the Metering trait

trait Metering {
  // any type with a method signature given below can be used for Metering
  type anyObserver = { def receiveUpdate(subject: Any) }
  private var observers = List[anyObserver]()
  def addObserver(observer: anyObserver) = observers ::= observer
  // we define a function literal with foreach
  def notifyObservers = observers foreach (_.receiveUpdate(this))
}

Here, the Metering trait allows observers to be added to it so that it can call a notify on the observers. We have defined implementations too in the trait unlike Java. The first line defines a type for an Observer. This is a structural type of the form { def receiveUpdate(subject:Any) }. Structural types specify only the structure a subtype must support. In our case, the structural type is defined by a having a method with a particular signature.
In our case let us add the observer be added as the following. It just increments the counter.

object DeviceCounter {
  var count = 0
  def receiveUpdate(subject: Any) = {
    count += 1
    println("Counter notified and the count is " + count)
  }
}

Now, let us look at the following code block

object ProvisioningService {
  def main(args: Array[String]) {

    val tablet = new Tablet
    tablet.setPrice(10.0)

    val phone = new Phone
    phone.setPrice(18.0)

    val anotherPhone = new Phone with Metering {
      addObserver(DeviceCounter)
      notifyObservers
    }
    anotherPhone.setPrice(89.0)
  }
}


We create an instance of Tablet and call its setPrice method. Since Tablet is mixed in with the Metering Trait, the counter would get incremented. Next, we instantiate a Phone and set its price. At this point the counter is not incremented since Phone does not mixin the Metering trait. But, look what we do next, we mixin the trait at the time of creating the next phone instance. Hence, the anotherPhone instance has the trait and hence also participates in the metering. This is something that we cannot do in Java. The result is the following output of code.

Price for the tablet is 10.0
Counter notified and the count is 1
the cost for the phone is 18.0
Counter notified and the count is 2
the cost for the phone is 89.0

Now, let us see how traits are constructed and what is their sequence of construction. Traits do not support auxiliary constructors. We cannot pass arguments to the primary constructor either to pass on values to the defined attributes. We can however, initialize fields with default values or leave them abstract so that the class with the trait can take care of it.

Let us look at the following code,

object Traitors {

  def main(args: Array[String]) {
    println("Creating the concrete class")
    new ConcreteClass
    println("After Creating the concrete class")
  }

  trait TraitOne {
    val x = "TraitOne"
    println("  in TraitOne: x = " + x)
  }
  trait TraitTwo {
    val y = "TraitTwo"
    println("  in TraitTwo: y = " + y)
  }
  abstract class BaseClass {
    val b = "BaseClass"
    def hello
    println("  in BaseClass = " + b)
  }
  class ConcreteClass extends BaseClass with TraitOne with TraitTwo {
    val c = "ConcreteClass"
    def hello = print("hello")
    println("  in ConcreteClass = " + c)
  }

}

When we execute the main method, we get the following output

Creating the concrete class
in BaseClass = BaseClass
in TraitOne: x = TraitOne
in TraitTwo: y = TraitTwo
in ConcreteClass = ConcreteClass
After Creating the concrete class

So, first the base class is constructed, then the trait one and trait two and then our concrete class. If we change the order of mixin of traits “with TraitTwo with TraitOne” instead of ” with TraitOne with TraitTwo” then the order of construction would change to TraitTwo being constructed before TraitOne.

So far so good and I really like the part where we can mixin a trait at the time of creating an instance. That is huge! Now onto something that I dont like. If I have a class in Java which implements two interfaces I would write it like

public class A implements InterfaceOne, InterfaceTwo{
}

Now, if I need to do the same thing in scala, i would have to write

class AnotherClass extends TraitOne with TraitTwo{

As you would notice, I have to use the extend keyword even if I am not extending any other class. If I am mixin only interfaces then the first interface is extended and then I can use with. Well, if you look at it other way it makes sense because Trait is not an interface and a class can extend Traits and a Trait can extend a class. So all in all it is good, just that like a few other things, it takes getting used to 🙂

As always, code is available on github.

Advertisements
Posted in: Scala