How To

How do you create a DSL? In all the things I’ve read about DSLs, I’ve never found a good explanation of the thinking behind creating a DSL. Debasish Ghosh’s “DSL’s in Action” came the closest but left me a little short. So I hope to do that in the next couple of posts. I’m only focused on embedded DSLs which are used in the context of a general purpose host language. External DSLs are a whole ‘nother subject.

Decomposition

Let’s say you live in an ideal world where requirements are fully specified up front. What’s the first thing you do after reading the requirements for the your next project? You break the problem up into smaller problems. And then break those up until you get a large number of problems you can actually solve. This is such second nature to me now that I don’t even think about it. But when I did start thinking about it, I saw that I was missing an obvious question.

At each level, how was I going to put these pieces together?

Rather than approach this thoughfully, I just assumed there would be functions or API’s and I would write the necessary glue code to stitch them all together. But I’ve since begun thinking this through rather than just assuming.

First step

And that is the first step towards creating a DSL. At each level of abstraction, think about the pieces that you’re going to compose. What is the simplest way to write that composition without regard to the syntax of the host language.

For instance, say I’m given the task of writing a web site to manage a small retail business. I might decide I need modules for accounts payable, accounts receivable, inventory control and a REST interface for the point of sale. The simplest way to say that would be:

(defroutes small-biz
    accounts-payable
    accounts-receivable
    inventory
    point-of-sale)

Anyone reading that code would have a pretty good idea what this piece of software is for. And other modules could be easily added. (Using ‘module’ in the most generic sense here.)

Composers

After thinking about what you’re going to be composing, you can start thinking about the how. What operators will you need? You can compose pieces sequentially, in parallel, with some sort of selection or repetition for example. Or maybe you just merge things together. Regardless, it’s important to have the results of those operators be values that can be used by future calls to your operators. This lets you build layers of abstraction and also assign meaningful names to pieces of functionality.

So, the bottom line is, make operators that compose your pieces to make larger pieces. But limit the numbers and kinds of operators. You don’t want to create a new general purpose language.

Saying something and hiding something

As you’re thinking about the what and how of composing, keep in mind what you’re really trying to say. You are building a language, after all. Beautiful code fulfills the requirements concisely and clearly. It states the solution to the problem in a form that is easy to read and understand.

But just as important as what you say, or maybe more so, is what you hide. For abstractions to have value, they have to hide complexity that would clutter up the stated solution. You want to have the architecture of your solution be seen, not all the scaffolding it takes to build it.

Boundaries

When you have a DSL, there has to be some way to seperate code written in it from code written in the general purpose host language. Otherwise, you’re just writing code in your host language and you really don’t have a DSL. So you need some kind of ‘run’ function that provides an interface between your DSL and the host language.

Also, as you decompose your problem, it’s probable that you can use a DSL across multiple levels of decomposition and also in different parts of the application.

And the ideal solution is to use a DSL that’s already been written, so keep your eyes open for DSLs that you might be able to use.

Wrapping up

To sum it all up: - Decompose your problem. Every level of abstraction is an opportunity to use a DSL. - Think about what you’ll be composing in order to build the solution at a that level. - Think about how you’ll do the composing, what operators will be needed. - Think about how you want to express the solution, what you want to say. - Think about what you want to hide. - Think about the boundaries of each DSL

Then repeat this process for each level of abstraction.

This post has been a lot of handwaving. Next post, we’ll start looking at concrete examples of DSLs and how to use them.

Jim Duey 20 February 2012
blog comments powered by Disqus