Saturday, March 29, 2008

Design Patterns in Scala

Design patterns in Scala

The GoF Design Patterns are perhaps the most popular design fads of Software ever. However some people argue that they are more or less solutions to the deficiencies of Language itself (particularly C++/Java) and if the language is powerful enough they are redundant. Consider this excellent presentation from Peter Norvig for example.
I agree that patterns translate to quite natural and concise code in some languages, but the intend of the pattern may still be important. So lets consider some of the popular patterns in Scala - a language which like Ruby/Lisp etc reduces Pattern implementation to triviality (though its not a dynamic language!).

1. Singleton
Singleton is one of the frequently (over)used patterns. In spite of being the simplest of patterns, it is surprisingly difficult to implement a 'pure' singleton in Java. The particularly famous Double-checked locking and so on. However, in Scala it is trivial. Lets say you need to create a Registry object which is singleton. You can do that in the following way:

object Registry {
def getEntry(): Entry {
}
//Other fields/methods
}
//Create and use the singleton
val entry = Registry.getEntry


Thats it! The only difference from defining a normal class is that instead of the 'class' keyword we used 'object'. If you have to map it back to java, this is equivalent of defining a class and all its fields/methods are static.

2. Strategy
Like any language where functions are first-class objects or where closures are available, Strategy pattern is obvious. For eg. consider the 'taxing' example:

trait TaxPayer
case class Employee(sal: Long) extends TaxPayer
case class NonProfitOrg(funds: BigInt) extends TaxPayer

//Consider a generic tax calculation function. (It can be in TaxPayer also).
def calculateTax[T <: TaxPayer](victim: T, taxingStrategy: (T => long)) = {
taxingStrategy(victim)
}

val employee = new Employee(1000)
//A strategy to calculate tax for employees
def empStrategy(e: Employee) = Math.ceil(e.sal * .3) toLong
calculateTax(employee, empStrategy)

val npo = new NonProfitOrg(100000000)
//The tax calculation strategy for npo is trivial, so we can inline it
calculateTax(nonProfit, ((t: TaxPayer) => 0)


3. Factory
Factory pattern addresses the fact that object constructors cannot return arbitrary objects. If you call constructor of object A, you always get an object A. If you need different objects based on parameter types, you typically create a Singleton Factory. In Scala also you need to do the same thing, but in a bit more elegant manner.

object Car {
def apply(String type) {
type match {
case "Race" => new RaceCar();
case "Normal" => new NormalCar();
case _ => throw new Exception;
}
}
}
//And you can create cars like:
val myCar = Car("Race")
//instead of more verbose
//Car myCar = CarFactory.getInstance().createCar("Ferrari");

Basically, you can exploit the fact that what apparently looks like a constructor call to a singleton object is syntactic sugar for the "apply" method.
4. Visitor
Visitor pattern is considered harmful by many people, but still it has its value at specific cases. Lets see a typical java implementation of Visitor pattern:

abstract class Expression {
...
public void accept(Visitor v);
}

class Identifier extends Expression {
}
class Sum extends Expression {
}
..

class Visitor {
public void visit(Identifier i) { }
public void visit(Sum s) { }
}

Typical java boilerplate code as you would expect. In Scala you can achieve the same result by using pattern matching:

trait Expression {
...
}

case class Identifier(value: Int) extends Expression {
}
case class Sum extends Expression {
}

object EvalVisitor {
def visit(expr: Expression): Int = expr match {
case (Identifier(v)) => v
case (Sum(e1, e2)) => visit(e1) + visit(e2)
}
}

The pattern matching code is also more flexible.
5. Decorator
Finally consider the Decorator pattern. Since Scala supports mixins and implicit conversions, Decorator is also simple and natural. Conceptually, there are two ways you can decorate an object - by adding new functionality and extending existing functionality.
To decorate a class with new functionality, you can use implicit conversions. Consider the RichInt class in scala standard library in action:

val range1to10 = 1 to 10

which in scala translates to 1.to(10). However, there is no method "to" in Int class. So how does it work? Scala has an innovative concept called Implicits for pimping your library. In essence it is Ruby's open classes but lexically scoped. Its interesting stuff and you can read about it elsewhere. For us, it suffices to say that there is an implicit conversion in Predef [implicit def intWrapper(x : Int) : RichInt] that does the trick.
Mixins are also good for decorating your classes and extend its functionality in different dimensions. For example consider the typical Reader interface:

trait Reader {
type T
def read: T
}

trait SynchronizedReader extends Reader {
abstract override def read: T = synchronized(super.next)
}

trait BufferedReader extends Reader {
abstract override def read: T = {
//buffering code
super.read
}
}

//A concrete implementation
class FileReader extends Reader {
type T = char
def read: char = ..
}

//Now we can mix in stuff that we need
//Create a FileReader
val f = new FileReader
//create a fileReader which is synchronized
val syncReader = new FileReader with SynchronizedReader
//create a fileReader which is synchronized and buffered
val bsReader = new FileReader with BufferedReader with SynchronizedReader


Conclusion
Design Patterns are good recipes for designing software. However, most of them generally solve a language issue than a design issue. If you have a good Language patterns (at least most of them) will become trivial. For example, in a structured language, the concepts of virtual methods or classes may be a 'Design Pattern'. Once you move to a powerful language, the design patterns that you deal with will also change. They will be at a higher level of abstraction. More on it later!

10 comments:

Robby O'Connor said...

good post

Unknown said...

Nice, that clears up a few ideas for me.

Andy Maleh said...

Great post. I agree that as older patterns become basic constructs in newer languages, new patterns emerge at higher levels of abstraction.

Anonymous said...

i dont agree that just because language is absorbing "design pattern", it was a language pattern and not design pattern.

bodhi said...

Anonymous: As I've mentioned in the post, the intend of the pattern still holds. Its only that the 'pattern' is obvious to mention. For eg. in C, OOP would be a design pattern. But in C++ it is a norm.

Anonymous said...
This comment has been removed by a blog administrator.
Olle Kullberg said...

Good work, but the examples could use some debugging:

1. For the strategy pattern I think the last line should be:

"calculateTax(npo, ((t: TaxPayer) => 0))"

2. For the factory pattern there are some errors. To make the example runnable you could make it look like this:

sealed abstract class Car {
override def toString = "YY Car"
}
case class RaceCar() extends Car{
override def toString = "YY RaceCar"
}
case class NormalCar() extends Car{
override def toString = "YY NormalCar"
}

object Car {
def apply(x: String): Car = {
x match {
case "Race" => new RaceCar();
case "Normal" => new NormalCar();
}
}
}

Then when you run the examples you can verify that the correct class has been created:

scala> val olles = Car("Race")
olles: Car = YY RaceCar

scala> olles.toString
res0: java.lang.String = YY RaceCar

Anonymous said...

what a interesting information, I gonna try this if doesn't work I tell so fast as I can, with this and viagra online maybe I can fix one or two problem that I have.

alex said...

Fantastic factor of perspective, I certainly not thought like this but you’ve opened my brain...

drug detox

Karlo tagalog said...

Quite possibly the most succinct and current info I came across about this subject. Sure pleased that I discovered that site by accident. I’ll probably be subscribing for your feed so that I will get the most current updates. Like the information here.
bank levy