Class Methods Part IV - DSL’s
This last post in a series on Ruby class methods focuses on Domain Specific Languages.
There are many different types of DSL’s. The broadest classification, coined I believe by Martin Fowler, distinguishes between internal and external DSL’s. External DSL’s are languages with a language specific compiler or interpreter. If you are using YACC or ANTLR to define the grammar of your DSL, you’re writing an external DSL. Nathan Sobo presented an interesting tool at RubyConf07 for defining language parsers and interpreters. Treetop allows you to create an external DSL using a grammar definition DSL and then easily define the interpreter in Ruby. Here is a simple phone number parser…
The really interesting bit is the phone_number method defined on the phone_number rule. The ability to add methods to nodes allows for a lot of exciting interpreter possibilities.
The advantage of external DSL’s is unlimited expressiveness. As long as you can define a parser for your language, you can express your intent in whatever syntax maps most closely to your domain. An interesting example of an external DSL is ANSI SQL. I’m not referring to the general purpose procedural monstrosities implemented by various vendors, but rather to the small language specific to the domain of set manipulation. Another example is Make for the domain of software builds. All that expressiveness comes at a high cost. The big disadvantage of external DSL’s is that you have to define the grammar, build the parser, and build the interpreter/compiler. If the author doesn’t define conditional statements, the language doesn’t have conditional statements, etc…
Internal DSL’s are languages whose definitions mandate syntactically correct programs for a “hosting” general purpose language. The hosting language’s interpreter or compiler is then responsible for program execution. The advantages and disadvantages of internal DSL’s are an exact mirror to those of external DSL’s. On the up side, because an internal DSL is defined within the context of an existing general purpose language, the language designer picks up a lot of functionality for free. Someone has already written a parser and interpreter/compiler for the host language, and provided all the common features that one expects from a modern language. This leaves the language designer free to focus on a compelling domain syntax for his or her little language. There is of course a catch, the host language puts immutable restrictions on the expressiveness of an internal DSL. This can be more or less of a problem depending on the natural expressiveness of the host language. Ruby is a fairly expressive. Syntactic features like optional braces, optional parenthesis, do end blocks and inline conditionals allow for creative authors to declare a fairly intuitive DSL syntax without violating Ruby grammar.
Within the field of internal DSL’s Ruby allows for a variety of styles. Jamis Buck has written an excellent introduction to these styles. We’ll focus here on the DSL’s style Jamis calls Class Macros.
The most prominent example of a class macro based DSL in Ruby is ActiveRecord…