I’ve been attending a course about Java Architecture in Caelum these last weeks. In one of those Saturdays, the instructor mentioned a little bit about DSLs. Better yet, he gave us an example using Scala =)
Now, the example was intriguing and interesting, at least for a Scala beginner like me, so I decided to translate it to English (the original was in Portuguese) and post it here. If you want, you can see the original in Portuguese, created by Sergio Lopes, here.
Before seeing the translated version of the DSL implementation, lets take a look on how you would use it – which is the most interesting part:
Tomorrow minus 1 month and plus 10 years and plus 1 day
Although the code above looks like a (almost?) proper sentence, it is valid Scala source code. That’s the beauty of writing DSLs in Scala =)
One thing that took me a while to understand regarding this code is the Conjunction part. Represented by the and instance in this case, its purpose is simple (at least after you understand it): pass a partial result to the next part of the calculation, when necessary. Notice in the code how months, years and days have one overloading that receives a Conjunction. This is what makes possible to yield a result that will be passed to the next calculation step.
Here is the full implementation code of the DSL:
import java.util.Calendar
class Date(val data: Calendar) {
data.clear(Calendar.HOUR)
import Date.Conjunction
private var last = 0;
def plus(num: Int) = { last = num; this }
def minus(num: Int) = { last = -num; this }
def +(num: Int) = plus(num)
def -(num: Int) = minus(num)
def months = { data.add(Calendar.MONTH, last); this }
def months(and: Conjunction): Date = months
def month = months
def month(and: Conjunction): Date = months
def years = { data.add(Calendar.YEAR, last); this }
def years(and: Conjunction): Date = years
def year = years
def year(and: Conjunction): Date = years
def days = { data.add(Calendar.DAY_OF_MONTH, last); this }
def days(and: Conjunction): Date = days
def day = days
def day(and: Conjunction): Date = days
override def toString = "%1$Td/%1$Tm/%1$TY" format data
}
object Date {
class Conjunction
val and = new Conjunction
def Today = new Date(Calendar.getInstance)
def Tomorrow = Today + 1 day
def Yesterday = Today - 1 day
def today = Today
def tomorrow = Tomorrow
def yesterday = Yesterday
}
The only thing I added to the code was operator overloading, so that the usage can be even more interesting, allowing stuff like this:
Today + 2 months
So, the def + and the def - are not present in the original code. I just added those as an exercise to understand how to use operator overload in Scala, which ended up being ridiculously simple. If you want to learn more about operator overloading, Joey Gibson has a nice blog entry about this here.
June 6th, 2010 at 9:04 pm
It became even better with the operator overload! Congrats! Sergio should update the gist
June 21st, 2010 at 8:51 pm
Paulo,
I’m wrecking my brain to understand this Conjunction thing. Can you please shed some more light on it? I’m an experienced Java programmer but newbee in Scala.
Thanks,
Dog
June 21st, 2010 at 8:58 pm
Never Mind! I got it!
So in case of “months”:
The first call is always months(and). So the method with the Conjunction in the signature is called first. Now the second call from here is the “plain” months.
Thanks! I loved it!
June 21st, 2010 at 9:40 pm
Ok, last comment …
The def + and the def – are not present in the original code because it breaks after the “and”. Try it!
Cheers!
June 21st, 2010 at 11:19 pm
Hey, thank you for the comments! =)
I’ll try it out for sure and perhaps write about it later.
June 22nd, 2010 at 1:46 am
I couldn’t understand exactly why this is happening, but seems to be related to the way Scala tries to infer who is the “owner” of the operator overload to call. I still want to come with a good solution, but if you try this:
(((Tomorrow – 1 month and) + 10 years and) + 1 day)
it works. At least, I think this test helps understanding a little bit what is happening.
August 15th, 2010 at 11:46 am
[...] program ini bukan buatan saya tetapi saya mengadopsi program yang dibuat oleh Sergio Lopes dan Paulo Renato dan saya mengubahnya dalam dialek bahasa [...]
March 6th, 2011 at 6:42 am
Since “and” is a singleton, why did you make it a “val” rather than an “object”?
March 8th, 2011 at 9:42 am
Good point. There is no special explanation for that. I just followed the original code design, while trying to understand how it worked. I added only the + and – definitions – and doesn’t work properly, as noted in some of the comments above.
Now, thinking about what you said, ‘and’ should probably be an ‘object’ – it would make the code a little bit cleaner.
November 21st, 2011 at 3:28 am
Pretty cool, thanks