By developers for developers.

Scala for the Intrigued

Using Traits

by Venkat Subramaniam

Scala traits remove duplication of code and provide a cleaner design.

Inheriting from a class or abstraction is common in OO programming, but Java restricts us to live with single implementation inheritance. Those of us who’ve tried multiple inheritance (MI) in languages like C++ know the consequences. MI is like a gorilla: it’s cute at first but you don’t bring it home. You have to deal with method collision and base instance duplication. While Java avoided these issues, simply prohibiting MI does not solve the real problem. In this tenth article of the series we’ll see how traits elegantly avoid the MI issues and provide an elegant modeling technique.

Our application that manages finance has gone through an initial audit and we’ve been asked to encrypt some data to increase security. The customer class we wrote,

class Customer(val firstName : String, lastName : String) {

//...

override def toString =

"Details for customer " + firstName + " - " + lastName

}

needs a special encrypt method. As a first attempt, we add that method in.

class Customer(val firstName : String, lastName : String) {

//...

def encrypt = {

// some complex logic to encrypt the data

toString.reverse

}

}

Here we have added an encrypt method in the Customer class. For this example we merely reverse the output of the call to the toString() method.

We can now directly call the encrypt method on an instance of Customer.

val customer = new Customer("John", "Doe")

println(customer.encrypt)

//eoD - nhoJ remotsuc rof sliateD

Soon, however, we realize that there are other classes in the application, like Stock, that requires this treatment. We know that duplicating the encrypt method in this and other classes is an act of criminal negligence. One possibile approach is to create an abstract base class and move the encrypt method into it. Then the Customer class can extend from that class. Unfortunately we quickly realize this will not work since the Stock class already has a base class, and not all securities may need the encrypt method.

class Security(val name : String)

class Stock(override val name : String) extends Security(name) {

override def toString = "Details for stock " + name

}

The Java way to solve this problem would be to create an interface. That still does not prevent the duplication, as each of the classes needs the implementation. We will have to resort to delegation to avoid duplication. In Java, we would create a class with a static method. In Scala, that would be a singleton object.

object Encryptor {

def encrypt(data : String) = {

// some complex logic to encrypt the data

data.reverse

}

}

Then in the Customer class we can invoke the encrypt method of the Encryptor.

class Customer(val firstName : String, lastName : String) {

//...

override def toString =

"Details for customer " + firstName + " - " + lastName

def encrypt = Encryptor.encrypt(toString)

}

val customer = new Customer("John", "Doe")

println(customer.encrypt)

//eoD - nhoJ remotsuc rof sliateD

The Stock class would have to do the same.

class Stock(override val name : String) extends Security(name) {

override def toString = "Details for stock " + name

def encrypt = Encryptor.encrypt(toString)

}

val stock = new Stock("XYZ")

println(stock.encrypt)

//ZYX kcots rof sliateD

The core logic for encrypt is encapsulated, but there’s a thin layer of duplication in the classes that use it. Let’s see how traits solve this problem.

Scala does not have interfaces; instead, it has traits. A Scala trait with only method declarations and no implementation translates to a mere interface at the bytecode level.

Let’s create a trait called Encrypt.

trait Encrypt {

def encrypt : String

}

Compile this trait using scalac and then view the generated bytecode using javap.

> scalac Encrypt.scala

> javap Encrypt

Compiled from "Encrypt.scala"

public interface Encrypt{

public abstract java.lang.String encrypt();

}

Scala traits directly map to Java interfaces. If we place implementations in the trait, then the Scala compiler creates an interface and an abstract base class for us. Let’s implement the encrypt method in our trait.

trait Encrypt {

def encrypt = {

// some complex logic to encrypt the data

toString.reverse

}

}

Compile this modified version of the Encrypt trait and take a look at the bytecode generated.

> scalac Encrypt.scala

> javap Encrypt

Compiled from "Encrypt.scala"

public interface Encrypt extends scala.ScalaObject{

public abstract java.lang.String encrypt();

}

> javap Encrypt\$class

Compiled from "Encrypt.scala"

publicabstractclass Encrypt$classextends java.lang.Object{

public static java.lang.String encrypt(Encrypt);

public static void $init$(Encrypt);

}

You probably have a hunch where this is going. Rather than requiring us to create the delegation, the Scala compiler takes care of it for us.

Now all instances of Stock also have the encrypt method. If we take a look at the bytecode generated for the Customer and Stock classes using the javap -c command, we will see the encrypt method added to each of these classes during compile time.

We can bring multiple traits into a class at the same time simply by using multiple withs in the class definition. In the next article in this series we’ll see how we can force method collisions between multiple traits and use it to our advantage.

Scala allows us to apply traits to instances also. Let’s look at a CheckingAccount that does not have the traits.

class CheckingAccount(val number : Int) {

override def toString = "Details for account " + number

}

val anAccount = new CheckingAccount(1)

println(anAccount.encrypt) //ERROR

Since CheckingAccount does not have any encrypt() method or the Encrypt trait, we can’t call the encrypt method on the instance we created. But even though the class does not have it, we can add traits to specific instances at the time of their creation.

val secretAccount = new CheckingAccount(2) with Encrypt

println(secretAccount.encrypt)

//2 tnuocca rof sliateD

While, in general, instances of CheckingAccount don’t have the encrypt method, our secretAccount is special and can be encrypted.

Traits in Scala are handled entirely at compile time. When you use instance-level traits, the compiler creates an anonymous inner class for that instance.

In this article we saw how traits remove duplication of code and provide a cleaner design. In the next article we’ll see how method collisions are handled and how we can put this to a greater use.

Dr. Venkat Subramaniam is an award-winning author, founder of Agile Developer, Inc., and an adjunct faculty at the University of Houston.

He has trained and mentored thousands of software developers in the US, Canada, Europe, and Asia, and is a regularly invited speaker at several international conferences. Venkat helps his clients effectively apply and succeed with agile practices on their software projects.