Combinators as the sublanguage of DSL syntax
Join the DZone community and get the full member experience.
Join For FreeIn my presentation on DSLs
in PhillyETE I had talked about organizing DSL syntax around a sub
language of combinators. The underlying implementation model needs to be
segregated from the engine that renders the syntax of your DSL. Doing
this you can decouple your semantic model and reuse it in the context of
other DSLs or even in some entirely different context. I called this
the model-view architecture of DSLs. In this post I would like to extend
my thoughts along similar lines and claim that publishing combinators
that wire your model elements is a very effective way towards
compositionality of your DSL syntax.
Functions compose naturally - hence if your DSL publishes combinators
then your client can compose them to form larger structures. But for
this composition to be effective, you need to have the proper types for
each of them - yes, we are talking about type-safe composition.
In Scala I tend to use the curried syntax a lot when I design
combinators. Let’s have a look at an example .. we assume that we have a
domain model designed using some paradigm - it can be OO, it can be
functional or maybe a mix of the two. When we implement a business rule,
we use these model elements, wire them up and present a proper syntax
to the user. As an example here's a domain rule for enrichment of a
securities trade as it goes through its processing pipeline ..
- get the tax/fee ids for a trade
- calculate tax/fee values
- enrich trade with net cash amount
Here are a few snippets of combinators that help users implement the rule ..
// get the list of tax/fees applicable for this trade
// depends on the market
val forTrade: Trade => Option[List[TaxFeeId]] = {trade =>
taxFeeForMarket.get(trade.market) <+> taxFeeForMarket.get(Other)
}
// all tax/fees for a specific trade
val taxFeeCalculate: Trade => List[TaxFeeId] => List[(TaxFeeId, BigDecimal)] = {t => tids =>
tids zip (tids ? valueAs(t))
}
val enrichTradeWith: Trade => List[(TaxFeeId, BigDecimal)] => BigDecimal = {trade => taxes =>
taxes.foldLeft(principal(trade))((a, b) => a + b._2)
}
Note each of these combinators return a function instead of a concrete business abstraction. Of course combinators are supposed to return a function only. But my point is that with such a strategy it becomes easier to build larger abstractions out of these smaller ones. And these combinators will be domain facing - each of them implements a snippet of a business rule. Hence take special care to name them appropriately so that you have that ubiquitous language going within your DSL syntax. Here’s how we combine the above to form a larger business rule that implements the “enrichment of trade” functionality ..
// enrichment of trade
// implementation follows problem domain model
val enrich = for {
// get the tax/fee ids for a trade
taxFeeIds <- forTrade
// calculate tax fee values
taxFeeValues <- taxFeeCalculate
// enrich trade with net amount
netAmount <- enrichTradeWith
}
yield((taxFeeIds map taxFeeValues) map netAmount)
// enriching a trade
trade map enrich should equal(…)
.. very concise, very succinct and brings out the domain semantics very explicitly. This composition was possible only because we used the curried syntax in designing our combinators. Haskell has curry-by-default - no wonder it revels in designing of embedded DSLs.
So, is combinators as the building block of DSL syntax a good idea to follow ?
I have been using this strategy for some time now and it has been working out quite nicely for me. It’s not essential that your underlying domain model has to be purely functional. I have been using libraries like scalaz in scala that offers a good hierarchy of typeclasses which help a lot in designing generic combinators that work across an unrelated family of abstractions. In my PhillyETE talk I discussed how functors allows you to map over entities across diverse abstractions like List, Option and Functions. And once you make your combinators generic, they tend to be adopted within the DSL as part of the ubiquitous language.
From http://debasishg.blogspot.com/2011/05/combinators-as-sublanguage-of-dsl.html
Opinions expressed by DZone contributors are their own.
Comments