Saturday, April 01, 2006

Scala: Compose Classes with Mixins

Scala is one of the most pure object-oriented languages with an advanced type system. It supports data definition and abstraction through class hierarchies along with efficient means for object composition and aggregation. In this post, we will go through some of the composition mechanisms supported by Scala, which make construction of reusable components much easier than Java.

However, before going into the details of all mechanisms, let us look at how Scala differentiates itself in expressing the basic construct of object oriented programming - the class definition. The following example is adapted from Ted Neward (with some changes) :

class Person(ln : String, fn : String, s : Person) {
  def lastName = ln;
  def firstName = fn;
  def spouse = s;

  def this(ln : String, fn : String) = { this(ln, fn, null); }

  private def isMarried() = {spouse != null;}

  def computeTax(income: double) : double = {
      if (isMarried()) income * 0.20
      else income * 0.30
  }

  override def toString() =
      "Hi, my name is " + firstName + " " + lastName +
      (if (spouse != null) " and this is my spouse, " + spouse.firstName + " " + spouse.lastName + "." else ".");
}

Let us quickly browse through the salient features of the above type definition where Scala differs from traditional OO languages like Java.

  • The main highlight is the conciseness or brevity as Ted Neward points out in his blog, where he also does an interesting comparison of the brevity of class definitions in Java, C#, VB, Ruby and Scala. Try writing the same definition in any of the mentioned languages - you will get the point straight through!

  • The class declaration itself takes the arguments for the primary constructor. The member definitions in the next three lines take care of the rest of the initialization. As a programmer, I have to type less - this is cool stuff! The constructor for an unmarried Person is defined as a separate constructor def this(...) ...

  • Like Ruby, Scala provides accessor methods directly around the fields. Being a functional language also, Scala does not provide default mutator semantics in the class definition. In order to mutate a field, you have to do it explicitly through state mutator methods.

  • Any class in Scala with an unspecified parent inherits from AnyRef, which is mapped to java.lang.Object in the Java implementation. The overriding of a method from the parent is explicitly decorated with the keyword override - the rest of the inheritance semantics is same as Java.


Class Composition in Scala

The rich type system of Scala offers powerful methods of class composition, which are significant improvements over that provided by popular OO languages like Java. Modular mixin composition of Scala offers a fine-grained mechanism for composing classes for designing reusable components, without the problems that techniques like multiple inheritance bring on board.

For effective class composition, Java offers "interfaces" in addition to single inheritance, while C++ offers direct "multiple inheritance" through classes. The problem with Java interfaces is that they contain only the method signatures, while the implementer of the interface has to provide implementation for all the methods that belong to the interface. Hence factoring out common implementations as reusable artifacts is not possible. OTOH, multiple inheritance in C++ offers direct and symmetric composition of classes without introducing any additional construct. But multiple inheritance brings with it the dreaded diamond syndrome - name conflicts and ambiguities. While this can be controlled by using virtual inheritance, still factoring out reusable functionalities in the form of composable abstractions is clumsy, at best, in C++. The idiom of using generic superclasses popularized by Van Hilst and Notkin, to implement mixins, provides a solution, but still this does not make mixins a part of the C++ typesystem. For more details, check this excellent treatment on the subject.

Scala's mixin-class composition allows programmers to provide default implementations at the mixin level itself, thereby reusing the delta of a class definition, i.e., all new definitions that are not inherited. Mixins in Scala are similar to Java interfaces in that they can be used to define contracts / signatures for subtypes to implement. The difference is that the programmer is allowed to provide default implementations for some of the methods which are parts of the mixin. While this may appear similar to what abstract classes provide in Java, but mixins can be used as a vehicle to implement multiple inheritance as well (which Java abstract classes do not offer). And finally, mixins, unlike classes may not have constructor parameters.

To define a class that will be used as a mixin for composition, Scala uses the keyword trait.

// defining a trait in Scala
trait Emptiness {
    def isEmpty: Boolean = size == 0;
    def size: int;
}

In order to compose using traits, Scala offers the keyword with :

// compose using traits
class IntSet extends AbstractSet with Emptiness with Guard {
    ....
}

The following are the main characteristics of trait types in Scala :

  • Trait classes do not have any independent existence - they always exist tagged along with some other classes. However they provide a seamless way to add behaviours to existing classes while allowing default implementations to be defined at the base level.

  • Nierstrasz et. al. observes
    An interesting feature of Scala is that traits cannot only be composed but can also be inherited, which is a consequence of the fact that Scala traits are just special classes. This means that both classes and traits can be defined as an extension of other traits. For example, Scala allows one to define a trait B that inherits from a trait A and uses the two traits U and V :

  • trait B extends A with U with V {
        ...
    }


  • In Scala, traits are first class citizens of the type system, in the sense that they are as much part of the type system as are classes. Traits, like classes introduce a new type and can be seamlessly integrated with the generics implementation of Scala. But that is for another day's story.


In order to get a feel of the power of mixin-composition in Scala using traits in the real world, here's an example from Odersky :

// define an abstract class for the iterator construct

abstract class AbsIterator {
    type T;
    def hasNext: boolean;
    def next: T;
}

// enrich iterators with an additional construct foreach

trait RichIterator extends AbsIterator {
    def foreach(f: T => unit): unit =
    while (hasNext) f(next);
}

// yet another iterator for Strings
// provides concrete implementations for all abstract
// methods inherited

class StringIterator(s: String) extends AbsIterator {
    type T = char;
    private var i = 0;
    def hasNext = i < s.length();
    def next = { val x = s.charAt(i); i = i + 1; x }
}

// usage
// composition of StringIterator with RichIterator
// Using class inheritance along with trait inheritance
// without considerations for multiple definitions
// : only delta is inherited

object Test {
    def main(args: Array[String]): unit = {
    class Iter extends StringIterator(args(0))
        with RichIterator;
    val iter = new Iter;
    iter foreach System.out.println
    }
}

Mixins in Ruby - Compared

Going thru the above piece, astute readers must have been wondering about how the mixin class composition in Scala stands with respect to some of the other implementations in modern programming languages. Let me end this post with a small comparison of the similar feature as offered by Ruby.

Mixin implementation in Ruby is offered through modules. Define a module in Ruby, include it within your class - and you have access to all instance methods of the module. It's as simple as that - when it's Ruby, simplicity is the essence.

// define a module
module Debug
    def who_am_i?
    "#{self.class.name} (\##{self.object_id}): #{self.to_s}"
    end
end

// include in class
class Phonograph
    include Debug
    # ...
end

class EightTrack
    include Debug
    # ...
end

// get access to instance methods
ph = Phonograph.new("West End Blues")
et = EightTrack.new("Surrealistic Pillow")
ph.who_am_i? // "Phonograph (#937328): West End Blues"
et.who_am_i? // "EightTrack (#937308): Surrealistic Pillow"

But, modules in Ruby have its own clumsiness when it comes to defining instance variables within the mixin and resolving ambiguities in names. Moreover, modules are not strictly part of Ruby's type system - Scala makes it more elegant by seamlessly integrating the trait with its class based type system. The moment you achieve this, generics based mixins come for free. And that will be one my topics for the next installment - another powerful abstraction that makes component design easier in Scala - The Abstract Type Member.

1 comment:

Unknown said...

Regarding the brevity aspect of Scala, I discovered the following post in the Scala mailing list, which suggests a further improvement. Adding a val modifier to the constructir parameters make them automatically available within the class. This is really cool stuff!