Victor is a ruby developer at Nulogy. He has worked a lot with Java and Ruby platforms. Being a big fan of domain specific languages he likes to blog about implementing them using Groovy, Ruby or Clojure. Victor is a DZone MVB and is not an employee of DZone and has posted 41 posts at DZone. You can read more from them at their website. View Full User Profile

Small DSL in Groovy

02.23.2011
| 5733 views |
  • submit to reddit

The flexibility of a programming language is one of the things I really like. Groovy is a flexible language. It doesn’t require you to spend a lot of of time solving every problem. You can do it in a neat and accurate way very quickly but the language allows you to evolve your solution after to make it more expressive and what’s really important, make it more reusable.

To illustrate it let’s look at a simple problem I’ve had recently. I was writing a translator from one language to another one and the last step of the translation was, of course, the optimization of the result output. I wrote a bunch of simple optimizers, most of them just grouped or sorted similar expressions that were located close to each other.

To make it simple, let’s take a look at this list of numbers: [1,3,6,4,10,2,5,3]. Each number is an expression. Even numbers are one type of expressions and odd numbers are the second type. If there are consecutive even numbers I want to sort them. And if there are consecutive odd numbers I want to return the product of them. So, after processing the list above should become [3,2,4,6,10,15] (it is because 1 * 3 = 3, sort(6,4,20,3) = (2,4,5,10), 5 * 3 = 15). It is so simple.

My first implementation was very simple too.

class Optimizer1 {
Closure criteria
private groups = [other: {it}]

def addGroupProcessor(String name, Closure c){
groups[name] = c
}

static withCriteria(Closure c){
new Optimizer1(criteria: c)
}

List process(List list){
def res = []
def buf = []
def currentGroup = null

for(item in list){
def group = criteria(item)
if(currentGroup == null){
currentGroup = group
}
if(group == currentGroup){
buf << item
}else{
res += processGroup(currentGroup, buf)
buf = [item]
}
currentGroup = group
}
res + processGroup(currentGroup, buf)
}

private processGroup(group, buf){
(groups[group] ?: groups.other)(buf)
}
}

It can be used this way:

def processedList = Optimizer1.withCriteria({it % 2 ? 'even' : 'odd'}).
addGroupProcessor('odd', {it.inject(1) { acc, val -> acc * val }}).
addGroupProcessor('even', {it.sort()}).
process ([1,3,6,4,10,2,5,3])

This particular example doesn’t look bad, but the real example I had included more groups and it didn’t look so well. In addition, the first implementation doesn’t elaborate the dynamic capabilities of Groovy. Trying to play with them I wrote the second implementation using methodMissing.

class Optimizer2 {
Closure criteria
private groups = [other: {it}]

def other(Closure c){
addGroup 'other', c
this
}

def methodMissing(String methodName, args){
if(args.size() == 1 && args[0] instanceof Closure){
addGroup methodName, args[0]
this
} else {
throw new MissingMethodException(methodName)
}
}

private addGroup(String name, Closure c){
groups[name] = c
}

static withCriteria(Closure c){
new Optimizer2(criteria: c)
}

List process(List list){
...
}
}

The code is very simple, if we try to invoke a method that doesn’t exists and we pass only one parameter (which is a closure), we assume that it is adding a new group processor. The result of this refactoring is that we don’t have to use the addGroupProcessor method anymore.

def processedList = Optimizer2.withCriteria({it % 2 ? 'even' : 'odd'}).
odd ({ it.inject(1) { acc, val -> acc * val } }).
even ({ it.sort() }).
process ( [1,3,6,4,10,2,5,3] )

Well, for me it looks a bit better, but there is a thing I can’t stand - too many braces. Really, I think there is is too much ({ here. To make it better we have to rewrite withCriteria one more time.

static withCriteria(Closure criteria, Closure handlersBlock){
def opt = new Optimizer3(criteria: criteria)
handlersBlock.delegate = opt
handlersBlock()
opt
}

What I’ve done is I’ve replaced a method chain with a closure. It is a common technique when you work on any kind of builders. Well, it is getting better:

def processedList = Optimizer3.withCriteria({it % 2 ? 'even' : 'odd'}){
odd { it.inject(1) { acc, val -> acc * val } }
even { it.sort() }
}.process([1,3,2,3,4,5])

So far so good, but can be done better. Instead of passing a closure we will pass a map from which it will retrieve all necessary data:

static process(Map conf){
def opt = new Optimizer4(criteria: conf.criteria)
m.rules.delegate = opt
m.rules()
opt.process m.list
}

def processedList = Optimizer4.process(
list: [1,2,3,4,5],
criteria: {it % 2 ? 'even' : 'odd'},
rules: {
odd { it.inject(1) { acc, val -> acc * val } }
even { it.sort() }
}
)

Though right know it already looks quite well there is some room for improvement even here. For example, we can do it this way:

def processedList = Optimizer5.process {
list([1,2,3,4,5])
criteria {it % 2 ? 'even' : 'odd'}
rules {
odd { it.inject(1) { acc, val -> acc * val } }
even { it.sort() }
}
}

Now I’m satisfied. First of all, we managed to eliminate all the noise we had before (all braces, colons and commas) and there is no duplication any more, no addGroupProcessor kind of calls.

Finally, we started with a simple and statically-typed implementation, then trying to make it better we introduced methodMissing and delegate; each iteration made our code more readable.

Of course, the example is oversimplified, but it demonstrates how easy to evolve your Groovy code applying different DSL techniques.

 

From http://vsavkin.tumblr.com/post/2969057213/small-dsl-in-groovy

Published at DZone with permission of Victor Savkin, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags: