Tuesday, August 16, 2011

A scala DSL technique - collecting

When coming up with a scala internal DSL, it is often that one will come up with constructs that produce things that need collected, in a list, tree or similar.

For instance, instructions for a robot:

$if (condition) {

move(1) :: move(2) :: move(3) :: Nil
move(1) + move(2) + move(3)
}


Due to syntax restrictions, it is often that people use operators to chain these, like the overloaded + above.

There is a technique you can use to make this look more familiar:

$if (condition) {

move(1)
move(2)
move(3)
}


While not the nicest, the idea is to use a static collector and collect the actions in there. You'll need levels, so that collecting nested blocks don't interfere, so there is a stack of collectors. Also, these collectors need to be thread-local so that different collecting threads don't interfere.

A reusable collector is in DslCollector.scala and a sample in DslCollectorTest.scala

For this pattern, you have a Collector, a Collectable and the actual constructs:

  case class move(i: Int) extends DslCollectable


def test1 = expect(move(1) :: move(2) :: Nil) {
val collected = new collection.mutable.ListBuffer[Any]()

DslCollector.collect { collected += _ }(1) { // start a collector level
move(1) //collects itself
move(2) //collects itself
} // collector ends

collected
}


You can then embedd this in a higher-level construct of your DSL, like the $if used in the example.

Enjoy!

0 comments: