Friday, June 22, 2007

Playing with Traits in Scala


Update: A more recent post on traits is also available

Traits in Scala provide a powerful tool for abstraction and class composition. Traits allow you to:
  • Define a type by specifying the signature of supported methods. This is similar to how interfaces work in Java.
  • Provide partial/full implementations that can be mixed-in to classes that use the trait. Why is this different from what abstract classes give you? The big difference is that abstract classes cannot be multiply extended from, while traits can.
This post starts to dig into how Scala traits work. I have used the following version of the Scala compiler for experimentation:
Scala compiler version 2.5.1-final -- (c) 2002-2007 LAMP/EPFL

Traits with only abstract methods get mapped to plain old interfaces by the Scala compiler. But when they contain implementations within non-abstract methods, traits start getting interesting. For such a trait (named, let us say - X), Scala generates two entities:
  • An interface called X with all the methods in the trait.
  • A class called X$class with static versions of all the non-abstract methods in the trait. These methods take one additional parameter (relative to the methods in the trait) of type X . Any code within a non-abstract method in the trait goes into the corresponding static method in X$class. Calls to other trait methods from within this code get translated to calls on the extra parameter of type X.
A class that mixes-in X ends up:
  • Implementing the interface X.
  • Delegating all the non-abstract methods of X to the static methods of X$class, passing itself (this) as a parameter.
So, mixing-in a trait provides (in Java terminology) a concise and powerful way to implement an interface and delegate its implementation to another class.

Let us see some of this in action. But first a detour through Java-land to look at a problem that can be nicely tackled with traits:

Let's say I have the following domain classes in a Java based system:
class Employee {
// etc
}

class Manager extends Employee {
List<Employee> directReports = new ArrayList<Employee>();

// etc
}
Now - let's say that I am given a new business requirement to implement:
whenever a new Employee starts reporting to a Manager, a specified list of departments need to be notified of this change.

Let's also say that I have these infrastructure classes available to me:
interface Observer<T> {
void onChange(Observable<T> source, T notificationData);
}

interface Observable<T> {
void addObserver(Observer<T> observer);
void removeObserver(Observer<T> observer);
void notifyObservers(T notificationData);
}

class ObservableImpl<T> implements Observable<T> {
// manages sequence of observers and notifications to these observers
}
What I really want to do at this point is to make the Manager class Observable. I can then fire a notification to an Observer whenever a direct report is added to the Manager; this Observer can notify the appropriate departments about the change.

Wouldn't it have been great if I could do this:
class Manager extends Employee, ObservableImpl<Employee> {

List<Employee> directReports = new ArrayList<Employee>();

public void addReport(Employee e) {
directReports.add(e);
notifyObservers(e);
}

// etc
}
Alas, we don't have multiple inheritence in Java. So I have to go ahead and implement the Observable interface within the Manager class - and delegate to an ObservableImpl:
class Manager extends Employee implements Observable<Employee> {

List<Employee> directReports = new ArrayList<Employee>();

Observable<Employee> observable = new ObservableImpl<Employee>();

public void addReport(Employee e) {
directReports.add(e);
notifyObservers(e);
}

// etc

// implement Observable methods by delegating to observable

public void notifyObservers(Employee notificationData) {
observable.notifyObservers(notificationData);
}

public void addObserver(Observer<Employee> observer) {
observable.addObserver(observer);
}

public void removeObserver(Observer<Employee> observer) {
observable.removeObserver(observer);
}
}
Traits in Scala give me a short-cut way of doing this. Here's the same functionality implemented in Scala:
trait Observer[T] {
def onChange(source: Observable[T], notificationData: T) : Unit
}

trait Observable[T] {
def addObserver(observer: Observer[T]) = {/*do the right thing*/}
def removeObserver(observer: Observer[T]) = {/*do the right thing*/}
def notifyObservers(notificationData: T) = {/*do the right thing*/}
}

class Employee {}

class Manager extends Employee with Observable[Employee] {

var directReports : List[Employee] = Nil

def addEmployee(employee: Employee) = {
directReports = employee :: directReports
notifyObservers(employee)
}
}
Notice how much more concise the Scala version is! All you need to do is to declare Manager as:
class Manager extends Employee with Observable[Employee]
and this mixes-in the functionality of the Observable trait.

Neat, huh?

I haven't yet talked about a very useful trait pattern - in which a trait defines an abstract method (which needs to be implemented by classes that mix in the trait), and then uses this method to provide higher level value-added methods. A future post will dig into this...

No comments: