Among the benefits of domain-specific languages is that, if designed well, less skilled programmers, and even business analysts and end-users, can use the DSL to interact with the system and construct programs from it.
One approach to defining a DSL is to invent a custom language with syntax that's custom-tailored to a business situation or requirement, and then write a parser that understands that language. Often, the parser will feed a syntax tree of a DSL program into a compiler that then translates the DSL into a general-purpose language, such as Java, or even Java bytecode.
By contrast, it is also possible to invent domain-specific syntax that can be used directly within a general-purpose language. Such syntax extensions differ from an API in that they provide a program model that especially suits a domain, such as Web applications or financial analysis. At the same time, users of that DSL can reach out to any facility provided by the general-purpose host language for tasks such as accessing other libraries or external resources and databases.
In a recent blog post, Designing Internal DSLs in Scala, Debasish Ghosh explains how to construct such an internal DSL with Scala as a host language. Because Scala interacts well with Java, this technique is especially well-suited to working with an existing enterprise Java application:
External DSLs involve parsing of syntax foreign to the native language - hence the ease of developing external DSLs depends a lot on parsing and parse tree manipulation capabilities available in existing libraries and frameworks. Internal DSLs are a completely different beast altogether, and singularly depends on the syntax and meta programming abilities that the language offers.
Ghosh defines a language for stock trading:
val orders = List[Order](
// use premium pricing strategy for order
new Order to buy(100 sharesOf "IBM")
maxUnitPrice 300
using premiumPricing,
// use the default pricing strategy
new Order to buy(200 sharesOf "GOOGLE")
maxUnitPrice 300
using defaultPricing,
// use a custom pricing strategy
new Order to sell(200 bondsOf "Sun")
maxUnitPrice 300
using {
(qty, unit) => qty * unit - 500
}
)
This DSL looks meaningful enough for the business analysts as well, since it uses the domain language and does not contain much of the accidental complexities that we get in languages like Java. The language provides easy options to plug in default strategies (e.g. for pricing orders, as shown above). Also it offers power users the ability to define custom pricing policies inline when instantiating the Order.
Ghosh lists several Scala features that make such a DSL easy to create, such as implicit and higher-order functions, currying, and methods that can act as operators.
What do you think of using Scala as a host language for internal DSLs?
> Among the benefits of domain-specific languages is that, if designed well, less skilled programmers, and even business analysts and end-users, can use the DSL to interact with the system and construct programs from it.
OK, let's test my assertion that the benefits of DSLs are not worth the costs. I'll try to rewrite some of the examples from the Camel page as best I can using an ideal statically typed language (I'll use GScript, our internal language)
Where we are passing an array of Routes to the RouteBuilder constructor. (The curlies inside the constructor indicate that an Array should be constructed.)
I'm assuming here that the creation of the class also registers the class in the underlying framework somehow.
A more complicated example is:
"direct:a" ==> { to ("mock:polyglot") choice { when (_.in == "<hello/>") to ("mock:english") when (_.in == "<hallo/>") { to ("mock:dutch") to ("mock:german") } otherwise to ("mock:french") } }
My naive first attempt at a library function similar to this in GScript is:
Yup, it's uglier, but the question is is it worth the costs of having a specialized language for routing, rather than just a library in a standard, existing language (with a good IDE supporting code-completion, one hopes.)
One thing I will say is that most general purpose imperative languages have a hard time expressing nested, hierarchically arranged objects nicely (Builder syntaxes start to fall down when things aren't linearized) but with a bit of work I think we could make them better at it.
If more than 25% (arbitrary but not to small number) of the code you have to write, are routing rules, it makes sense to create a DSL. More general: if the effort to create (and maintain and use!) a DSL is smaller than the effort to write the code in some normally used language X, it is worth the effort.
Wouldn't it be better to compare the DSL to a language that is "available" (instead of an internal language, where nearly no one can say, if your example is good w.r.t. the features of that language?).
> OK, let's test my assertion that the benefits of DSLs are > not worth the costs.
Doesn't that question apply to everything? You have to weigh the cost and benefit on a case by case basis. How much effort does it take to create and maintain the language compared to the things written *in* the language?
For example, given the mock translations below for my language of choice, how much code and time would you approve of to implement such a dialect/DSL if the number of routes you expect to write is:
1) less than 10 2) from 10 to 100 3) from 100 to 500 4) more than 500
(anyone case respond, the more the merrier)
RouteBuilder builder = new RouteBuilder() { public void configure() { from("seda:a").to("seda:b"); } };
rte: route [seda:a to seda:b]
class MyRouteBuilder extends RouteBuilder { "direct:a" --> "mock:a" "direct:b" to "mock:b" }
rte: route [ direct:a to mock:a direct:b to mock:b ]
RouteBuilder builder = new RouteBuilder() { public void configure() { from("seda:a").choice().when(header("foo").isEqualTo("bar")).to("seda:b") .when(header("foo").isEqualTo("cheese")).to("seda:c").otherwise().to("seda:d"); } };
rte: route [ seda:a to [ seda:b when header "foo" is "bar" seda:c when header "foo" is "cheese" seda:d otherwise ] ]
I'm not sure I would use a DSL in this case myself. It would depend on the desired functionality.
@Carson: What's the difference between an "internal DSL" and a library? AFAICT almost nothing except they read marginally better, and that depends entirely on the implementation language.
If you had attempted your argument against *external* DSLs you'd have a stronger point.