I am learning Groovy during the process of implementing a DSL to study malaria resistance. This is a first post, of hopefully many, where I layout some tactics on implementing DSLs in Groovy. Take my suggestions with a grain of salt, as I am nothing more than a newbie. I will address small issues with each post. Expect a strong technical leaning discussing picky details…
Most of what you read is actually not my creation or ideas, but they come from the extremely helpful Groovy users mailing list, especially from Guillaume Laforge.
Numbers, script name space and named parameters
Our first objective is very simple: to model a drug compound (to treat malaria), say:
1 2 3 4 | Ronald.init(this) cq = compound(name: "Chloroquine", abbreviation: "cq", halfLife: 83.h) //I am not sure about 83 hours at all |
Lets start with the compound half life parameter (the time that takes to eliminate half of the drug concentration from the body). Notice the 83.h? Meaning 83 hours. How do we do this, considering that numbers are not time? A first implementation used Categories (an option you might want to explore), but my current one uses the ExpandoMetaClass:
Integer.metaClass.getH << { -> new Time (delegate, Time.HOURS) }
There are several details worth noticing:
- First, we are adding a new method to the Integer class, i.e., the Integer class will be changed in behavior, a pretty nuclear change eh…
- We are adding a getter method, so, when you do 4.h Groovy will call the getter method getH, using the typical Java naming convention.
- Notice that we don’t return an Integer but a Time object (a vanilla object defined elsewhere). For me this caused a click in my mind: Calling an integer to get something else.
- Notice the delegate word, a way to access the object being manipulated.
- On the clumsy side, you will probably will have to do the same thing for the BigDecimal meta class if you want to support floats. Numbers off all kinds don’t have a common ancestor (they are actually Java classes), I suppose for performance reasons.
Now, lets go back to line 1 of the first code snippet, the initialization. One of the things init does is changing Integer and BigDecimal, but it is not the only thing it does: It also changes what is available on the script name space. There are actually a few ways of doing that, but the two others recommended but Guillaume required to have some boot up code in Java, and for now, in prototype phase, I am too lazy to do that. Just for information google for CompilerConfigurate#setScriptBaseClass() or check the Groovy Java-side Binding class. As for me, I just passed the script environment to the init function:
1 2 3 4 5 6 7 8 9 | static init(env) { env.compound = {Map args -> Compound c = new Compound(args['name']) c.abbreviation = args['abbreviation'] ?: null c.halfLife = args['halfLife'] ?: null return c } //... } |
So, we just added a closure with name compound (a function, eh) to the script name space. The closure simply creates a new Compound. Nice syntatic sugar…
By the way, do you notice on line 2 that Map thingy? Lets talk a bit about named and optional parameters (and how, in my view, they suck in Groovy). Lets get a more explicit example:
writeFile (file: 'out.txt', mode: 'append')
Now, the way writeFile is supposed to be written is quite strange
void writeFile (Map args) { file = args['map'] //...
Am I doing some kind of newbie mistake here? This is a strange way of attaining the result. Typing information is completely lost from the function signature (that is bad for smart IDEs, automated code tools and introspection). What is wrong with the ye old way of Python (where you list all parameters in the signature, assigning default values to optional parameters)?
2 Comments to "DSL tactics in Groovy (1/many)"
Please share your thoughts
Filed in: groovy











> On the clumsy side, you will probably will have to do the same thing for the BigDecimal meta class if you want to support floats. Numbers off all kinds don’t have a common ancestor
They have: java.lang.Number.
You can use this type for your EMC but you need to call
ExpandoMetaClass.enableGlobally()
beforehand such that inheritance lookup kicks in for the EMC.
keep groovin’
Hi,
I am using Groovy a few weeks now and really like all sugar sintax.
For example, you can use the “as” operator for creating instances:
cq = [name: “Chloroquine”, abbreviation: “cq”, halfLife: 83.h] as Compound
is the same of setting properties one by one (lists work for constructors on “as” operator).
and access values on map with “dot” operator:
Compound c = new Compound(args.name)
c.abbreviation = args.abbreviation ?: null
c.halfLife = args.halfLife ?: null
and go on…
Thanks,
P.S. Groovy reminds me 10 years ago when I learned C++98 knowing C… now Java seems like C, so old!