Tuesday, June 03, 2008

Internal DSLs: Part 1

Ruby as a language helps rapid application development by facilitating composition and use of internal DSLs (domain specific languages) without the need to write complex parsers and compilers.

Rails, a popular Ruby web framework, benefits quite a bit from that fact as it has a DSL for specifying object model relationships, a DSL for naming pluralization rules, and a DSL for routing of web URLs to actions.

RSpec, a very popular Ruby testing library, allows developers to write unit-tests and functional tests with a syntax that is very close to plain English, making test code easy to maintain and useful as specifications of the software.

So, DSLs help developers raise the level of abstraction and reduce the input needed from users/developers to the absolute minimum necessary for getting the job done.

As an example from Glimmer, a new Eclipse Technology Project for SWT GUI development, here is how a UI author would create a shell with a label:

shell {
  label {text "Welcome!"}
}

As you can see, this is the bare minimum needed to visually represent a shell containing a label.

Coding the same example in plain Java yields the following:

Shell shell = new Shell(Display.getDefault());
Label label = new Label(shell, SWT.NONE);
label.setText("Welcome!");

With the Java API, the developer has to be concerned with lower-level details, such as object assembly mechanisms and setting values following the JavaBeans standard, neither of which are truly UI authoring concerns.

Here is another example taken from Rake, a popular Ruby make-like build tool:

task :generate do
  # code generation logic
end

task :load => :generate do
  # database data loading logic
end

task :test => [:generate, :load] do
  # test running logic
end

What is noteworthy about the Rake DSL is the visual factor of the syntax. Arrows are used to indicate dependency in a similar fashion to how one might use arrows in a hand-drawn diagram.

Without a DSL, the tasks could have been written as:

def generate
  # generate running logic
end

def load
  generate unless generate_ran
  # load running logic
end

def test
  generate unless generate_ran
  load unless load_ran
  # test running logic
end

As you can see, with purely imperative logic, the developer works at a lower abstraction level by having to manage the task run-order dependencies. While it may seem simple in that example, that can bloat to pages of code with enterprise Rake scripts, increasing the code surface and probability of making mistakes.

Therefore, not only does relying on internal DSLs help developers raise the level of abstraction, but it also minimizes the amount of code needed, thus reducing the probability of making mistakes and increasing productivity further.

Nonetheless, with all the excitement about internal DSLs, people may wonder... How do they compare to external DSLs? Where does their use really fit in? Can they be used by stakeholders or are they limited to developers only? Are there other alternatives?

Stay tuned for answers to these questions in future blog posts.

1 comment:

Gergely N said...

Thanks, keep this cool stuff coming!