This was all straightforward enough, in fact early in my career I moved (fairly) freely from programming in BASIC, Fortran, C and Pascal. In the early 90s I moved into the CAD (Computer Aided Design) / PLM (Product Lifecycle Management) industry, specifically the AutoCAD industry. It was at this point I was introduced to a language which, at first, completely foxed me. This language was Lisp, specifically AutoLisp.
At a first view, Lisp looked quite alien, endless parenthesis, strange functions such as car, cons and cdr, but once I got to grip with he basics the power of the language became apparent. The key to Lisp are sets and lists, in fact Lisp stands for List Processing. Here's a bit of sample Lisp code:
1 2 3 4 5 | (setq cntr 1) ( while (cntr < 5) (princ cntr) (seqt cntr (+ cntr 1)) ) |
Lisp lends itself to programming AutoCAD quite well, as it was set and list orientated and with a CAD system a lot of the programming was aimed at manipulating geometry of the CAD model / drawing.
Anyway, back to homoiconic. The clever bit about Lisp is the fact that functions can be treat in the same way as variables, that is, functions can be passed as function arguments and returned as function values. It's this key property that allows powerful abstractions to be built in a language like Lisp, hence it use as a DSL. As always an example helps:
1 2 3 | (defun double (x) (* 2 x) /* define a function double that simply times the argument by 2 */ ) |
1 | (double (double (double (double 1)))) |
1 2 3 4 5 6 7 | (defun repeat-transformation (F N X) /* Repeat applying function F on object X for N times */ ( if (zerop N) /* Returns true if the argument is zero */ X (repeat-transformation F (1- N) (funcall F X)) ) ) |
I'm not planning to do in-depth programming in Lisp here. If you're interested in learning more about Lisp I recommend you take a look at Practical Common Lisp by Peter Seibel. The text for the book can be found on-line here. Hopefully, though, you've got the idea that a programming language can, in essence, manipulate itself to create a new subset language, hence a Domain Specific Language.
So what use are DSLs? A good real life practical example is Apache Camel. Camel is an integration framework that provides a implementation structure and runtime for the classic Hohpe and Woolf Enterprise Integration Patterns (EIP). Integration is a great application for a DSL as you have a common patterns to solve and components to 'wire' together. In Camel's case, the common elements are wiring Processors that carry out functions such as transformation, mediation, routing etc, with Endpoints that allow messages to be sent and received, e.g. JMS, HTTP etc.
To demonstrate how productive and powerful a DSL (and in this case Camel) can be, imagine you've got a systems integration problem to solve which involves reading files from one directory and placing them in another. Simple right? Here's a possible solution in Java.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | public class FileCopier { public static void main(String args[]) throws Exception { File inboxDirectory = new File( "data/inbox" ); File outboxDirectory = new File( "data/outbox" ); outboxDirectory.mkdir(); File[] files = inboxDirectory.listFiles(); for (File source : files) { File dest = new File( outboxDirectory.getPath() + File.separator + source.getName()); copyFile(source, dest); } } private static void copyFile(File source, File dest) throws IOException { OutputStream out = new FileOutputStream(dest); byte [] buffer = new byte [( int ) source.length()]; FileInputStream in = new FileInputStream(source); in.read(buffer); try { out.write(buffer); } finally { out.close(); in.close(); } } } |
Now here's the sample problem solved with Camel DSL.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class FileCopierWithCamel { public static void main(String args[]) throws Exception { CamelContext context = new DefaultCamelContext(); context.addRoutes( new RouteBuilder() { public void configure() { from( "file:data/inbox?noop=true" ) .to( "file:data/outbox" ); } }); context.start(); Thread.sleep( 10000 ); context.stop(); } } |
For another good example of application of DSL is FIT (Framework for Integrated Test). FIT looks to solve that eternal software engineering problem of testing and test scripts and closing the gap between developers, business analysts and end users. FIT reads HTML tables that map onto system classes methods and properties. A Business Analysts or End User creates a FIT document that describes the tests to be carried out in the form of the table with inputs and expected outputs. The FIT document can be created in Microsoft Word and exported as HTML. Developers then create Fixtures which map the FIT documents onto the business logic code of the application being tested.
In the case FIT, the DSL is, what FIT term, the Fixture. Fixtures wrap the specific application business logic that allows the FIT documents to execute the tests. A simple Fixture class can be seen below.
1 2 3 4 5 6 7 8 9 | import fit.ColumnFixture; public class Division extends ColumnFixture { /* Extending the ColumnFixture class allows the FIT runtime to map the input test tables to the logic code */ public float numerator; public float denominator; public float quotient() { return numerator / denominator; } } |
Increasingly, there are tools becoming available for DSL construction. A good example of one of these is Meta Programming System (MPS) from JetBrains.
So what's the future for DSL? I believe they have a place. I'm not sure there are too many advantages in trying to build DSLs for business applications, they are just too varied to gain extensive reuse and may be too unfamiliar for use by end users. The Apache Camel project shows the way for me with DSLs, accelerating the development of common development patterns, Groovy is also being used for this kind of 'plumbing' application.
If you want to found out more about DSLs I'd recommend visiting Martin Fowler's Wiki and taking a look at his book Domain Specific Languages.