Saturday, September 15, 2007

... And miles to go before I sleep.

Domain-specific languages are quite important these days, and Groovy is suited extremely well for implementing them.
A DSL is a language that allows expressions in a domain-specific manner (you could be more precise and more theoretical, but let us be pragmatic). Examples for DSLs are music scores, chess moves, or expressions regarding time and duration or space and distance.

A Domain-Specific Language

Let us take the last example, and think about possible uses of working with distances. What we would like is something like this:
3.m + 2.yd + 2.ml - 1.km
Or like this:
3.mi > 3.km

And a notation to simply convert distances to different units would be nice:
3.mi.km
meaning 3 miles as kilometers.

The interesting point is that with Groovy, this is quite simple to implement.

The Plumbing

We need support for the notation .unit, we need support for plus() and minus() operations, and we have to support comparisons between different distances. This involves converting from one unit to others. And we need a Distance and a Unit type. Before you say, oh God, way too much, let me tell you that the total number of lines to implement this DSL is less than 90 lines of source code.

Let us start with the Distance type. We need a reference to a Unit object and a length, an implementation of the methods plus() and minus(), an implementation of the methode compareTo() (which implies that Distance has to implement the interface Comparable) and a toString()-method. The conversion of distances in different units is simply delegated to the Unit class, since that class should know best.
class Distance implements Comparable {
BigDecimal length
Unit unit

Distance plus(Distance operand) {
def newLength = this.length + Unit.convertUnit(operand, this.unit)
new Distance(length : newLength, unit : this.unit)
}
Distance minus(Distance operand) {
def newLength = this.length - Unit.convertUnit(operand, this.unit)
new Distance(length : newLength, unit : this.unit)
}
int compareTo(other) {
if(this.unit == other.unit)
return this.length <=> other.length
return this.length <=> Unit.convertUnit(other, this.unit)
}
String toString() {
"$length $unit.name"
}
}

Nothing special here.

Now our Unit class should contain some predefined Unit objects representing the supported units. We have already seen that the Unit object needs a name. Furthermore it gets a ratio compared to the meter.
In principle the ratio would be a single value and we could compute different unit simply by dividing and multiplying in the correct order. But we have the problem that values are represented with finite precision. So to get our conversion as precise as possible we define a table that allows direct conversion with only one operation instead of two or three.

The ratio now is the row index in the table. To convert between different unit in principle would simply mean to access the ratio table for the coefficient and then compute the conversion. Since the conversion coefficients for different directions of the same unit combinations (e.g. m -> mi and mi -> m) are reciprocal, the table contains the coefficient only for one direction and a 0 for the other direction. If we access the table and get a 0, we do the inverse operation with the other coefficient.
There might be better and easier to extend ways to implement the conversion, but this one is fairly simple.

class Unit {
def ratio
String name

static def convertUnit(Distance d, Unit newUnit) {
def factor = ratioTable[d.unit.ratio][newUnit.ratio]
if(factor)
return d.length * factor
else
return d.length / ratioTable[newUnit.ratio][d.unit.ratio]
}
static ratioTable = [
// mm, cm, m, km, y, mi
[ 1, 0, 0, 0, 0, 0 ], // mm
[ 10, 1, 0, 0, 0, 0 ], // cm
[ 1e3, 1e2, 1, 0, 0, 0 ], // m
[ 1e6, 1e5, 1e3, 1, 0, 0 ], // km
[ 914.4, 91.44, 0.9144, 0.9144e-3, 1, 0 ], // yd
[ 1.609344e6, 1.609344e5, 1.609344e3, 1.609344, 1760, 1 ], // mi
]

public static final mm = new Unit(ratio : 0, name : "millimeter")
public static final cm = new Unit(ratio : 1, name : "centimeter")
public static final m = new Unit(ratio : 2, name : "meter")
public static final km = new Unit(ratio : 3, name : "kilometer")
public static final yd = new Unit(ratio : 4, name : "yard")
public static final mi = new Unit(ratio : 5, name : "mile(s)")
}

Finally we define constants for all the units we support (mm, cm, m, km, yd, mi).

Now we have everything we need as plumbing. We can add and subtract different Distance objects using the + and - operator, we can compare distances with the <, > and == operators and we can convert different distances.

Creating the DSL

To create our DSL we use a category. For each of the supported units we implement the method get() for the types Number and Distance. Be defining the method for Number objects we can use the notation "3.2.mi", by defining the method for Distance objects as well we can convert on the fly using the notation "3.2.mi.km". The respective implementations are straightforward.

class DistanceCategory {
static Distance getMm(Number n) { new Distance(length : n, unit : Unit.mm) }
static Distance getMm(Distance d) {
new Distance(length : Unit.convertUnit(d, Unit.mm), unit : Unit.mm)
}
... (repeat for cm, m, km, yd, mi)
}

And that's it. We could also try to generalize the get()-methods, but for such a simple DSL this is not worth the time.

Using the DSL

Using the DSL is as simple as using any other category:
use(DistanceCategory.class) {
def d1 = 1.m
def d2 = 1.yd
def d3 = 1760.yd
def d4 = 100.cm
println d1 + 1.yd
println 1.yd + 1.mi
println 1.m - 1.yd
println d2.m
println d3.mi
println d4.m
println 1000.yd.km
println 1000.yd
The result of the different println statements is the following:
1.9144 meter
1761 yard
0.0856 meter
0.9144 meter
1 mile(s)
1 meter
0.9144000 kilometer
true
Cool, isn't it? In less than 90 lines of source code we have a full DSL for describing distances, for adding and subtracting them, and for comparing them.

Next steps could be to allow multiplication and division as well, but then it gets interesting. Imagine the product of 1.m, 2.yd and 3.mi.

Credits

I took the idea for this DSL from a talk given by Bernd Schiffer at the JFS 2007. Bernd, I hope you don't mind.

This blog's title is the last line of a poem by Robert Frost (in case you wondered).

Thursday, August 16, 2007

A Timer for Everything (A TimerCategory)

While writing the chapter about the GDK, I found that the support for the Timer was less than perfect.

First Question: What is a Timer? A timer is an object that contains a thread of its own that works on TimerTask instances, calling them either once or repeatedly. To create a TimerTask object you derive a new class that implements the run() method, create an object and you are ready to start this. The Timer class offers multiple methods, that start a timer task once after a delay, after a delay and then repeatedly (either fixed rate or interval-based), using Date instances or longs as arguments.

While there is a method added to Timer that starts a closure once after a given delay (the method name is runAfter()), there is no such method for repeated execution.

My first idea was to add the missing functionality simply by adding methods to the GDK, which I gaily proposed to do on the Groovy mailing list.

The answer (I'm paraphrasing): The methods are too clumsy. Something DSL-like would be much better e.g.,
Thread.start(in: 20.minutes, every: 1.hours) { ... }

Point taken. This is much more elegant, simpler to use and most probably better to extend.

Second Question: How to implement this? It is quite simple actually. We begin by stealing (another term is reusing). Groovy already has a TimeCategory in org.codehaus.groovy.runtime, that allows things like
20.seconds + 3.minutes.from.now
Pretty cool stuff. See Groovy Dates and Times for details.

Now, instead of attaching the new functionality to Thread we will add it to Timer objects. This way the user still has the option to decide to which Timer object the resulting task will be attached. Furthermore, we do not have to keep some global timer instance that is used to start the different tasks.

We need the following keywords:
  • at
    value should be Date, long or a duration (from the TimeCategory)
  • in (alias after)
    values should be Date, long or a duration
  • every
    values should be long or a duration
  • fixed
    true or false (fixed rate or interval-based rate)

With these you can do all of the following:

t.run(at: new Date(), every: 3.seconds, fixed:true) { ... }
t.run(at: 3.minutes.from.now) { ... }
t.run(in: 5.seconds, every: 3.seconds, fixed:true) { ... }
t.run(after: 5.seconds, every: 3.seconds, fixed:true) { ... }
t.run(after: 5000, every: 3.seconds) { ... }

We need, of course, both the TimeCategory and the TimerCategory to do this:

use(TimeCategory, TimerCategory) {
t = new Timer()

t.run( ... ) {
do the stuff
}

Fine, now for the realization.

To implement a category we need a class that has static methods. The first parameter of a static method denotes the type to which the method will be attached at runtime. So, if I use the Timer class as the first parameter, I later can call the method on a Timer object. In the method I have access to the object on which the method was called (it is the first argument).

The method has two real parameters, a map that contains values for the keys at, in, every and fixed, and a closure. Thus it looks like the following (simplified):

class TimerCategory {
static TimerTask run(Timer timer, Map vals, Closure c) {
task = new ClosureTimerTask(c)
if vals.fixed
get val and set fixed
if vals.after or if vals.after or vals.at
get value and set delay
if vals.every
get value and set period
use values to schedule task with timer
return task
}
}

The real implementation of the category has 45 lines, so there's not much to it. The unit test has over a hundred lines ...

So, by implementing a new category in 45 lines instead of added another few methods to the GDK we actually have made Groovy more elegant. Try to do something like this with Java.

This is part of why I love Groovy so much. You not only have fun programming with it, but also when you find a small imperfection. Maybe especially then.

Third Question: When will you implement your next Category?
Happy hunting ...

You can download the code here.