Wednesday, September 29, 2010

The Strategy pattern and Traits

So I promised I would talk about patterns and traits. When I wrote about the concepts of the Controller and the RuleSet, I already hinted at using the Strategy pattern. I was right, because that turned out to be exactly what I needed. And I was wrong, because the classical Strategy pattern is an ill match for Scala.

In this post I will explain how I implemented the Strategy pattern using Scala traits, and how this turned out to be an unsatisfying approach.

Recap: what is this all about?

The plan is that all external interactions with Scalasim simulations is done through a Controller object. However, the effect of such interactions should be independent of the controller itself, and ideally be pluggable at runtime.

At this point, the prime example of such an effect is adding citizens and assigning them to available workplaces. As stated in a previous post, each existing city builder simulation has its own rules. So, by extracting these rules into a separate RuleSet entity, it becomes possible to emulate these existing games without modifying the Controller.

The Strategy pattern

To me, the description above sounds like the Strategy pattern. According to Wikipedia:

The strategy pattern is intended to provide a means to define a family of algorithms, encapsulate each one as an object, and make them interchangeable. The strategy pattern lets the algorithms vary independently from clients that use them.

In many cases, functional programs implement the Strategy pattern by simply passing around functions that implement a concrete algorithm; this is also explicitly stated on the Scala patterns Wiki. However, in this case the Strategy does not just consist of one function, but of many functions that have to be called at different moments. So I decided to use Traits to implement this feature.

A view from afar

The outline of my plan looked as follows. On the one hand, there was going to be a Controller class:

class Controller {

  /** Add an actor to the simulation. */
  def addActor(a: ScalasimActor): ScalasimActor = { ... }

  /** Advance the simulation by one step. */
  def tick() { ... } 

}

Next to that, there was going to be a RuleSet trait which would be subclassed for each concrete set of algorithm. Clients of the simulation would then simply say:

val controller = new Controller() with SpecificRuleSet

which at least looks very natural.

The gory details up close

I started out by writing a nice Spec and something I called the FixtureRuleset – a RuleSet implementation explicitly written for my unit tests. Everything looked natural. The unit tests failed, but this was to be expected. After all, I hadn't implemented a single line of code.

I implemented the Controller class as outlined above, and started on my FixtureRuleSet:

trait FixtureRuleSet { 
  
  override def addActor(a: ScalasimActor): ScalasimActor = {
    super.addActor(a)
    // additional stuff
    a 
  }

}

Which doesn't compile.

The problem of course is the fact that the FixtureRuleSet stands on its own – its only superclass is ScalaObject, which does not contain any method called addActor. So I tried to use self types:

trait FixtureRuleSet { 
  self: Controller =>
  
  override def addActor(a: ScalasimActor): ScalasimActor = {
    super.addActor(a)
    // additional stuff
    a 
  }

}

Which doesn't compile either. Luckily, at the same moment, this very problem was posted on the scala-user mailing list.

[...] self-typing only restricts the type the traits may be applied to, but the self-type information is not used by the compiler to determine the type of the 'this' reference.

Changing super.addActor to self.addActor did not improve the situation – this turned out to cause infinite recursion, followed by a StackOverflowError.

The final solution

I ended up with the following outline:

class Controller extends AnyRef with RuleSet {

  def addActor(...): ... = {
    // do controller stuff
     
    actorAdded(...)  // callback to the RuleSet
  }
}

trait RuleSet {

  /** callback */
  def actorAdded(...)

}

trait FixtureRuleSet {
  self: Controller => 

  def actorAdded(...) { ... }
}

val c = new Controller() with FixtureRuleSet

It works, and the resulting call is indeed what I wanted, but it all felt terribly wrong. The spaghetti of callbacks did not increase the clarity of my code. It made me a sad panda.

Not knowing how to proceed, I spent some more time reading about Scala in various blogs. Until I read the following quote on Ruminations of a Programmer:

The power of mixins for adding orthogonal features ..

Yes, orthogonal. As Wikipedia says:

Orthogonality guarantees that modifying the technical effect produced by a component of a system neither creates nor propagates side effects to other components of the system.

Which sums it up nicely.

Lessons learned

Scala Traits can be very useful, but they don't seem to be the most suitable choice for implementing the Strategy pattern. In my next few posts, I am planning to do some redesign of the current program, and I believe I will find a better solution.

In the meantime, I've learned a lot about how Scala and its compiler work, which is always a Good Thing (TM).

Get the code

As usual, the code can be found on code.google.com. As stated above, I am not particularly happy with the current solution, so no SVN tag this time. If you really want to check it out, use revision r22.

No comments:

Post a Comment