Is it a bird? Is it a plane? It's a DSL!

Published 15 July 07 06:11 AM | andersnoras 

My latest posts on DSLs caused a bit of a stir around the blogosphere. Some of of the more constructive debates have been on wether a fluent interface is just a fluent interface or if it can be labeled as a DSL. I have a consultant's answer to this; it depends. I my opinion my posts on the planning DSL concerns a DSL, even if it technically is just a fluent interface. Scott Bellware has an argument that is hard to disagree with - "Whether fluent interface is a form of DSL or not, it's obviously a form of fluent interface.". Scott's contributions to the debate are much concerned about the confusion one might introduce by mixing the terms internal DSL and fluent interface. The fluent interface is the pattern in use, a DSL is more of a design style and it is here my key motive for calling this a DSL lies. In addition to using a fluent interface to allow users to express their intentions in a form that is close to a well formed (albeit long and tedious) English sentence, I also used a number of other design techniques to make the fluent interface more expressive. The most important of these is the humane interface. A humane interface differs from what we are used to within the .NET and Java worlds because they are larger and are stuffed with convenience methods that aren't technically needed to get the job done. These interfaces have the additional verbosity to make it more pleasant to work with. Martin Fowler's article on the topic is a great read.
Still from a patterns point of view, the pattern used in the example in question is the fluent interface pattern. Whether it is written in C# or any other language this is one of the central patterns being applied within internal DSLs. Others include variations on the builder and factory patterns, which I will return to later in this post. To give people a better understanding of what the are doing I think we who are promoting DSLs have a great responsibility for making people see the connections to other OOP techniques. I appreciate Scott's effort to make this clear, and even if I've never met him - I'll gladly shake your hand if I ever do. :-) 

I fully agree that more dynamic languages like Ruby, Groovy or Boo lend themselves better to internal DSL development than static languages like C# or Java, but unfortunately many developers do not have the option to choose these because of corporate policies or other limitations. I stated my venture into writing DSLs with statically typed languages to help people think inside a bigger box to improve their code and make it more user friendly by applying some of the techniques that have made dynamic language so popular, so fast.
When it comes to developing DSLs the ability to implement methods at runtime, for instance via Ruby's magical method_missing,  is a huge advantage with dynamic languages. If C# had sported the recurrence branch of the language could had pure interface like;
Recur.Once, Recur.Twice and Recur.ForthyFiveTimes rather than a Times() method in combination with "once" and "twice" properties for convenience.
Apart from the obvious "dynamicity" of dynamic languages, many of these also have lax rules on whether you have to use parenthesis or not. This can often be leveraged to make a DSL more readable, but you'll never get a true English sentence with an embedded DSL - it will be interrupted by language specific punctuation. Consider this snippet from Specter:

specify people.Must.SomeSatisfy({ p | return p.Name == "andrew" })

This shows a common pattern seen in internal DSLs. Joe Oscampo's NUnit.Behave, which is a pure C# DSL, uses a fluent interface to achieve something similar:

Scenario("savings account is in credit");
            Given("my savings account balance is", 100,
                  delegate(int accountBallance)
                      {
                          savings = new Account(accountBallance);
                      });

            Given("my cash account balance is", 10,
                  delegate(int accountBallance)
                      {
                          cash = new Account(accountBallance);
                      });

            When("I transfer to cash account", 20,
                 delegate(int transferAmount)
                     {
                         savings.TransferTo(cash, transferAmount);
                     });

            Then("my savings account balance should be", 80,
                 delegate(int expectedBallance)
                     {
                         Assert.AreEqual(expectedBallance, savings.Ballance);
                     });
            Then("my cash account balance should be", 30,
                 delegate(int expectedBallance)
                     {
                         Assert.AreEqual(expectedBallance, cash.Ballance);
                     });

Both of these uses closures, but the C# syntax is much more verbose than its Boo equivalent. If you're to do the same thing with Java its even worse.
The point is that any internal DSL will be obscured by the limitations of the language. If you want a clean, reads like plain English DSL, you'll have to resort to an external DSL. If you writing a parser is an option you can take this very far. Consider this example from the Daily WTF's recent programming contest.

Let's assume that tab equals 8 spaces
     Let's draw characters randomly
     Let's allow user to make mistakes

     Now we'll define numbers
 
     Zero is
          a large circle

     One is
          either
               a long vertical line
          or
               a long vertical line
               a very small slash line
               top of first joins top of second
 

     Two is
          either
               a part of circle 12 to 6
               a long horizontal line
               6 of first joins left of second
          or
               a part of circle 9 to 5
               a slash line
               a long horizontal line
               5 of first joins top of second
               bottom of second joins left of third

When speaking of external versus internal DSLs, one argument that came up was that internal DSLs aren't true DSLs because they don't introduce any new keywords into a language and hence its just an API style. I believe that this is only partially true, and again it depends on the capabilities of the hosting language. C# is very limited in this sense, but Java 1.5+ and Visual Basic have their static imports which can be exploited to do this. I've written about how one can achieve this in Java in an earlier post, so let us redo this little DSL in Visual Basic. First we'll need a class that defines the different keywords of the language. Since we'll be leveraging Visual Basic's static imports feature this needs to be a class with shared (which is VB-speak for static) methods.

Public Class XmlBuilder
    
    Private Shared Property doc As XmlDocument
        Get
            Dim lds As LocalDataStoreSlot = Thread.GetNamedDataSlot("doc")
            Dim instance As XmlDocument
            instance = CType(Thread.GetData(lds),XmlDocument)
            If (instance = Nothing) Then
                instance = New XmlDocument
                Thread.SetData(lds, instance)
            End If
            Return instance
        End Get
        Set
            Dim lds As LocalDataStoreSlot = Thread.GetNamedDataSlot("doc")
            Thread.SetData(lds, value)
        End Set
    End Property
    
    Public Overloads Shared Function Document() As XmlDocument
        Dim result As XmlDocument = CType(doc.CloneNode(true),XmlDocument)
        doc = Nothing
        Return result
    End Function
    
    Public Overloads Shared Function Document(ByVal rootNode As XmlNode) As XmlDocument
        doc.AppendChild(rootNode)
        Return Document
    End Function
    
    Public Overloads Shared Function Element(ByVal name As String, ParamArray ByVal childNodes() As XmlNode) As XmlElement
        Dim elm As XmlElement = doc.CreateElement(name)
        For Each node As XmlNode In childNodes
            If (node = XmlAttribute) Then
                elm.SetAttributeNode(CType(node,XmlAttribute))
            Else
                elm.AppendChild(node)
            End If
        Next
        Return elm
    End Function
    
    Public Overloads Shared Function Element(ByVal name As String, ByVal value As Object) As XmlElement
        If (value = XmlNode) Then
            Return Element(name, CType(value,XmlNode))
        End If
        Dim elm As XmlElement = doc.CreateElement(name)
        elm.InnerText = Convert.ToString(value, CultureInfo.InvariantCulture)
        Return elm
    End Function
    
    Public Shared Function Attribute(ByVal name As String, ByVal value As Object) As XmlAttribute
        Dim attr As XmlAttribute = doc.CreateAttribute(name)
        attr.InnerText = Convert.ToString(value, CultureInfo.InvariantCulture)
        Return attr
    End Function
    
    Public Shared Function Text(ByVal value As Object) As XmlText
        Dim elm As XmlText = doc.CreateTextNode(Convert.ToString(value, CultureInfo.InvariantCulture))
        Return elm
    End Function
    
    Public Shared Function Comment(ByVal comment As String) As XmlComment
        Return doc.CreateComment(comment)
    End Function
End Class

This is a straight port from my Java example. Read this to get an explanation of what the code does. The above class is tailored to be used as keywords within a client application, but the trick to achieve this lies in the client code. The first thing we'll need to do is import the static methods of this class.

Imports XmlBuilder

After doing this, we can use the different methods as "keywords" within our client application to build an XML document.

Dim doc As XmlDocument
doc = Document( _
    Element("Employees", _
        Element("Employee"), _
            Comment("Highly valued employee..."), _
            Element("Id", 42), _
            Element("Name", "Arthur Dent"), _
            Element("ContactInfo", _
                Element("PhoneNumber", "555-5555"), _
                Element("Address", "Upda road 42"), _
                Element("Country", "England")), _
            Element("Employee", _
                Attribute("toBeFired", True), _
                Element("Id", 3.14), _
                Element("Name", "Lay C. Slobb"), _
                Element("SpecialChars", "< & > @"))))

This isn't as domain specific as the upcoming XML literal features of Visual Basic 9 or Java 1.7, but it is much more convenient than mucking about with the DOM API to create the XmlDocument instance.

Even if this never has been the intention for static imports, they can be "misused" to introduce new "keywords" into a language. Combining static imports and fluent interfaces in internal DSLs introduce some interesting opportunities. LINQ is really syntactic sugar over a clever API, and the query language is a brilliant example of compiler macro DSL and is in that sense similar how Boo and Groovy macros work.  Still, the value of LINQ is the ability to have a common query language for anything that implements IEnumerable or IQueryable. I've done quite a bit of work to do something similar with Java, and since Java doesn't have any intrinsic support for compiler macros, I've resorted to combining static imports and fluent interfaces to build my DSL. Below is an example of a client using this experimental DSL to query a collection of domain objects.

Product[] products = Product.getAllProducts();
Object[] expensiveInStockProducts = (Object[])
        from(products).as("p").
                where(
                        and(
                                gt("p.unitsInStock", 0),
                                gt("p.unitPrice", 3)
                        )).select("p");

This examples uses all of the techniques discussed in this posting, and if you look aside from the parenthesis, dots and semicolons the lines are blurred between what is Java and what is an API. Because of this I believe that this more than just a couple of factories, some builders and a fluent interface - it's a DSL.

Filed under: , , , ,

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# Scott Bellware said on July 15, 2007 7:42 PM:

Great, thoughtful post.  Thanks!

# Stig said on July 16, 2007 1:59 AM:

What is the point with making it so complicated to render XML? Why can't just just do this?

Dim xml As XmlDocument = New XmlDocument

xml.LoadXml("<root>" & _

   "<foo>bar</foo>" & _

   "<bar>foo</bar>" & _

   "</root>")

# andersnoras said on July 16, 2007 4:45 AM:

@Stig;

There is a significant difference between building an object model, like the XML DSL does and parsing a string to an XML Document. With the DSL, the structure is evaluated at compile time while in your example things are evaluated at runtime. In additional you miss out on all of the support for string formatting and escaping and similar when you use string concatination to build an XML structure.

# ElComplicato said on July 18, 2007 2:24 AM:

This is excellent!! I think that it would be harder to complicate such a simple thing and still be able to bill for it. Congratulations! This is as good as the old one of the lawyers charging by the word.

getting ready to up my hourly rate, he he

# andersnoras said on July 18, 2007 2:49 AM:

@ElComplicato;

I'm not sure which simple thing you are referring to here. Is it any of the samples provided in this article or is it something else?

# John Rusk said on August 21, 2007 1:50 AM:

Workarounds to static imports....

# Anders Norås' Blog said on January 30, 2008 12:52 PM:

Today it is exactly one year since I pick up on blogging after a long break. To celebrate, I&#8217;ll

Leave a Comment

(required) 
(optional)
(required) 
Enter the code you see below