Václav is a programming enthusiast who's constantly seeking ways to make development more effective and enjoyable. He's particularly interested in server-side Java technologies, distributed systems, concurrency, agile methodologies, modern programming languages and DSLs. He works for JetBrains as a senior software developer and a technology evangelist. He is also a board member of the JetBrains Academy. On the side, he's leading the GPars project, an opensource concurrency library, and investigates the domains of neural networks, evolutionary programming and data mining. You can check out his blog or follow him on twitter. [dzone] Václav is a DZone Zone Leader and has posted 45 posts at DZone. View Full User Profile

Speak To Your Domain

07.08.2008
| 7862 views |
  • submit to reddit

Domain Specific Languages (DSL) have been gaining popularity steadily for quite some time. We'll jump right to some little experiments, but those who'd prefer to get sound theoretical background, please check out the Martin Fowler's article or wikipedia.

It's all about money

Here I'm going to play around with simple and intuitive examples of internal DSLs defined in Groovy. First, check out this little piece of Java code:

Money m1=new Money(10, "EUR");
Money m2=new Money(10, "USD");
Money m3=m1.add(m2.to("EUR")).to("GBP");

How quickly can you get what the code does? Two instances of the Money class with different currencies are created. After converting m2 into EUR the two instances are added and then the result is converted into GBP currency. It wasn't that difficult, was it? And how about the next piece, does it do the same thing?

Money m3=new Money(10, "EUR").add(new Money(10, "USD")).to("EUR").to("GBP");

The correct answer is it doesn't. You'll get a runtime exception for adding money instances with different currencies. Note also that even though we're using a statically typed language here, the compiler cannot check violations of our business logic (adding different currencies) for us.
Let's try again. Is the following piece of code doing the same thing as the original example?

Money m3=new Money(10, "EUR").add(new Money(10, "USD").to("EUR")).to("GBP");

Yes, now it is correct, but I definitely need some time to make sure I understand the code and get all the parentheses right.
Now let's try the same thing, but using a simple Groovy-based DSL.

Money m1=10.eurMoney 
m2=10.usdMoney 
m3=(m1 + m2.eur).gbp

How difficult was it to understand the code? In my opinion, it was at least a bit easier then in the Java case, but I've been trained to read Groovy code, which makes me not very objective. I'd be very interested in other people's opinion.
Now, let's try with the one-line examples. First the one with error of adding money with different currencies. Can you spot the error easily?

Money m3=(10.eur + 10.usd).eur.gbp


The reduced number of parentheses together with property-based syntax makes the code structure more obvious.
And now the correct variant:

Money m3=(10.eur + 10.usd.eur).gbp


Note that following a couple of simple principles makes a general-purpose language, like Groovy, suitable for internal DSLs. These are in particular loose syntax regarding parentheses, semicolons and the return keyword, operator overloading and direct property access. Meta-programming also helps a lot by enabling us to add extra methods or properties at runtime.
Another useful concept are named parameters, which let you specify any combination of values for properties at object creation time, without having to define constructors for all the possible variants. So code like this:

process(new Order(new Modey(167, "USD"), new Money(19, "USD"), null, 0), true)

can be replaced with this:

process(new Order(netPrice:167.usd, tax:19.usd), true)

Sending money is easy, receiving them is fun

And finally a more involved concrete example from an accounting application. To transfer money from one account to another, you typically need to write code like this:

Money money = new Money(amount: 10, currency: 'eur')
getAccount('Account1').withDraw money
getAccount('Account3').deposit money

The code must run within a transaction, typically as part of a transactional service, and should be also protected with security constraints. If transferring money between accounts is very frequent in your application, why not to introduce a DSL, which would make the code shorter and more readable? The same money transfer could be expressed as:

"Account1" >> 10.eur >> "Account3"

This is still valid Groovy code and can be put anywhere in your application, so pretty easily the code can be made secure and transactional. As being valid Groovy code, you can use your existing IDE and the build environment to edit, test, debug and refactor it. The code might be quickly turned into something like:

retrieveSourceAccountNumber(order.customer) >> order.totalPrice >> getOurPaymentAccount()

And of course the DSL can be also combined with control logic or iterations:

["Account1", "Account2"].each{it >> 10.eur >> "Account3"}

or

employees.each{"Our_Company_Account" >> 1000000.eur >> it.salaryAccount}

The presented DSL builds on the ability of numbers to express money through dynamic properties, plus the ability of the right shift operator on String to retrieve the account by identifier and perform the actual money transfer. These capabilities are, however, not available by default. We have to add them, which means we have to create a DSL.
Defining such a DSL isn't much work. We only need to teach numbers to work with currencies and Strings to deal with accounts and money transfers. I found the Groovy concept of categories very handy for this purpose.
For example, adding currency-related properties to numbers is as simple as defining a single Java or Groovy class.

class MoneyCategory {
    static Money getEur(Number num) {new Money(amount:num, currency:"eur")}
    static Money getUsd(Number num) {new Money(amount:num, currency:"usd")}
    static Money getCzk(Number num) {new Money(amount:num, currency:"czk")}
    static Money getGbp(Number num) {new Money(amount:num, currency:"gbp")}

    static Money getEur(Money money) {money.to("eur")}
    static Money getUsd(Money money) {money.to("usd")}
    static Money getCzk(Money money) {money.to("czk")}
    static Money getGbp(Money money) {money.to("gbp")}
}

And now within use blocks you can use the properties like in this example:

use (MoneyCategory) {
    product.setPrice(10.eur)
}


With DSLs in my toolbox I can see new horizons ahead. And they are fantastic.

From http://www.jroller.com/vaclav

Published at DZone with permission of its author, Václav Pech.
Tags:

Comments

Brian Sayatovic replied on Tue, 2008/07/08 - 7:07am

As a non-Groovy developer, I found the succint Groovy syntax difficult to read.  Only the accompanying English explanations made it clear.

Money m3=(10.eur + 10.usd).eur.gbp  

The ".eur" in "10.eur" means "construct a new Money with value 10 and currency EUR"?  But the ".eur" at the end of the line means "convert a Money to the EUR currency" (presumably using some ExchangeRateService internally).  So ".eur" is very context sensitive, and I don't find the context very plain.  And how did the "10.eur" know to construct a Money instead of a Whatsit or a Widget?

Actually, I found your first syntax to be far clearer:

Money m1=new Money(10, "EUR");
Money m2=new Money(10, "USD");
Money m3=m1.add(m2.to("EUR")).to("GBP");

While some may complain that its 3 lines instead of 1, its a very unambiguous 3 lines.  The types are clear, construction is clear and conversion is clear.  And in reality, I'll never have such literal coded in my application.  Instead, it'll be wrapped up in a parameterized method with a business intent, e.g.

public Money calculateTotal(Money basePrice, CardProcesssor bank, Currency targetCurrency) {
 Money proccessingFee = bank.getProcessingFee(basePrice.to(bank.currency));
 Money total = basePrice.add(processingFee.to(baseprice.currency);
 return total.to(targetCurrency);
}

Sebastian Jancke replied on Tue, 2008/07/08 - 8:52am

For me, both versions work (Java Embedded DSL and Groovy DSL). I also get the "10.eur" -thing. As it read's like plain english. For convenience, you may add "to_eur" for conversions, to make the conversion more clear and distinguish it from money-construction with currency=eur.
Last thing to say: I've never worked with Groovy or Ruby before, although I'm really DSL-affine.

-Sebastian
  

Václav Pech replied on Tue, 2008/07/08 - 9:45am

Thank you, guys, for your opinion. We could definitely have choosen different name for conversion, so the code would look something like 10.usd.toEur or Money m3=(10.eur + 10.usd.toEur).toGbp instead, to avoid confusion.

Vaclav

 

Václav Pech replied on Tue, 2008/07/08 - 10:13am

Regarding string literals for currencies, I can imagine their frequent use, for example, in tests
assertEquals(100.usd, order.total)

as well as in some type of business rules, like e.g.

if (moneyToTransfer.toEur > 100.eur) doSomething...

In my opinion, in such situations the DSL syntax can improve readability quite considerably.

 

Vaclav

 

Karim Elsayed replied on Tue, 2008/07/08 - 10:45am

I just dont see it, sorry

you if condition will still look nice

if(moneyToTransfer.toEur() > euro(100))

euro() a small method to create a Money object of type euro return new Money(x,"EURO")

toEur() is a small method to convert to euro.

clean and still plain java..  perhaps you dont like the literal  EURO, but your DSL has to include EURO specific conversions as well right?

 

 

 

Václav Pech replied on Tue, 2008/07/08 - 12:16pm

Karim, I think, you've got it right. You've just created a simple handy DSL, this time in Java, which makes your code more readable. BTW, the > operator would not work on Money class in Java the way you use it.

Karim Elsayed replied on Tue, 2008/07/08 - 2:51pm

I dont want to disappoint you :) I didnt mean operator overloading.. I just meant these methods could return a primitive type

to be honest, I do like some features in groovy but I am no groovy fan.. and I am in favour of slowing down the pace of changes to the langauge syntax and focus more on standardisation... 

Alex(JAlexoid) ... replied on Tue, 2008/07/08 - 7:56pm

"Account1" >> 10.eur >> "Account3"  
What is that exactly? Put "Account1" into 10.eur?
Withdraw is quite tricky, since it's not that easy to present.

10.eur >> "Account3" 
May be understood for cerdit account, but
 "Account1" >> 10.eur

is definitely something strange.(not clear)
Probably better solution would be:

"Account1" - 10.eur >> "Account3"  

Jesper Nordenberg replied on Wed, 2008/07/09 - 3:23am

Here's probably how I would write it in Scala:

(Money[EUR](10) + Money[USD](10).to[EUR]).to[GBP]

Note that you will get a compile time error if you try to add money of different currencies, and that you can easily add new currencies without modifications to the API. This is possible through the use of implicit objects.

Václav Pech replied on Wed, 2008/07/09 - 6:47am

Jesper, although I'm not fluent in Scala, your example looks pretty interesting to me. If I understand correctly you use currency as a type parameter to the Money class. Is that right? How complex is it to define such a small DSL in Scala?

Jesper Nordenberg replied on Wed, 2008/07/09 - 8:15am

It's very easy to define the DSL in Scala, here's an example (slightly simplified from my previous example):

object Currencies {
trait Currency

case object USD extends Currency
case object EUR extends Currency
case object GBP extends Currency

def exchangeRateUSD[C <: Currency](c : C) : Double = c match {
case USD => 1
case EUR => 1.3
case GBP => 1.5
}

case class Money[T <: Currency](amount : Double, currency : T) {
def to[U <: Currency](newCurrency : U) = Money(amount * exchangeRateUSD(currency) / exchangeRateUSD(newCurrency), newCurrency)
def +(m : Money[T]) = Money(amount + m.amount, currency)
}

implicit def toRichCurrency[C <: Currency](c : C) = (v : Double) => Money(v, c)

def main(args : Array[String]) = {
println("Amount: " + (EUR(10) + (USD(10) to EUR) to GBP))
}

}

Note that you can write "USD(10) to EUR", making the code very readable. This is possible because of the dot-less method invocation in Scala, and the implicit conversion from a Currency to a function "Double => Money" (the toRichCurrency function in the code).

Note also that it's very easy to add a new Currency, just define a new case object anywhere in the code that is imported.

Václav Pech replied on Wed, 2008/07/09 - 9:15am

Thank you Jesper for sharing the Scala code. I find it very interesting and promising at the same time. It combines some of the Scala powerful tricks like case classes, pattern matching or implicit conversion so I can see how they can play together. Very nice!

 

Marcos Silva Pereira replied on Fri, 2008/07/11 - 12:36am

I try to implement it using expando and overrind getProperty for Money class:

class Money {

String currency
BigDecimal amount

Money to(newCurrency) {
new Money(amount: this.amount, currency: newCurrency)
}

String toString() { "$currency $amount" }

Object getProperty(String name) {
def metaProperty = Money.metaClass.getMetaProperty(name)
if(metaProperty) return metaProperty.getProperty(delegate)
else return to(name.substring(3))
}

}

Integer.metaClass.getProperty = { name ->
new Money(amount: delegate, currency: name)
}

println 10.usd.to_eur

Course, I skip the logic about convert from a currency to another.

Kind Regards

Václav Pech replied on Fri, 2008/07/11 - 11:42am

Great comment, thanks for posting this example. It avoids hard-coding currencies and handles them truly dynamically.

I personally tend to prefer Categories where applicable, as they allow me to reuse Java classes for my cross-cutting functionality and limit scope of the DSLs to the use() blocks only, but meta-programming has a lot to offer as well. Recently I found two promising articles on what comes soon into Groovy - Groovy dynamic stateless mixins and Groovy dynamic stateful mixins.

 

sdf ghj replied on Thu, 2009/07/23 - 10:41am

thanks for your post.perhaps you will like Tiffany

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.