The drive toward DSLs : Taking a DSL apart—what makes it tick?

6/8/2012 11:33:59 AM
We’ve looked at building DSLs from the point of view of the outward syntax—how we use them. What we haven’t done is cover how they’re structured internally—how we build and integrate them into our applications.

In general, a DSL is composed of the building blocks shown in figure 1.

Figure 1. A typical DSL structure

A typical DSL is usually split into several distinct parts:

  • Syntax— This is the core language or the syntax extensions that you create.

  • API— This is the API used in the DSL; it is usually built specifically to support the DSL and its needs.

  • Model— This is the existing code base we reuse in our DSL (usually using a facade). The difference between the API and the model is that the model usually represents the notions in our application (such as Customer, Discount, and so on), whereas the API focuses on providing the DSL with convenient ways to access and manipulate the model.

  • Engine— This is the runtime engine that executes the DSL and processes its results.

The language and the API can be intrinsically tied together, but there is a fine line separating the two. The API exposes the operations that DSL users will use in the application. Usually you’ll expose the domain operations to the DSL. You express those operations through the language, but the API is focused on enabling a good syntax for the operations, not on providing the operations themselves.

We need to understand what features of the language we can use and what modifications we’re able to make to the language to better express our intent. Often, this is directly related to the API that we expose to the DSL. As I mentioned earlier, if you’re working in a domain-driven design manner, you’re in a good position to reuse the same domain objects in your DSL Often, though, the API will be composed of facades over the application, to provide the DSL with coarse-grained access into the application (fine-grained control is often too fine grained and is rarely useful in a DSL).

Keeping the layers separated

Several times in the past I have tried to combine different parts of the DSL—typically the syntax and the API—usually to my regret. It’s important to keep each layer to itself, because that brings several advantages.

It means you can work on each layer independently. Enhancing your API doesn’t break the syntax, and adding a method call doesn’t require dealing with the internals of the compiler.

You can use the DSL infrastructure from other languages, as well. Why would you want to do that? Because this will avoid tying your investment in the DSL into a single implementation of the syntax, and that’s important. You may want to have several dialects of a single DSL working against a single infrastructure, or you may decide that you have hit the limits of the host language and you need to build an external DSL (or one using a different host language). You’ll still want to use the same infrastructure across all of them. Having an infrastructure that is not tied to a specific language implementation also means that you can use this infrastructure without any DSL, directly from your application.

A typical example of using the DSL infrastructure without a DSL language would be an infrastructure that can also be used via a fluent interface to the application and via a DSL for external extensibility.

The execution engine is responsible for the entire process of selecting a DSL script and executing it, from setting up the compiler to executing the compiled code, from setting up the execution environment to executing the secondary stages in the engine after the DSL has finished running (assuming you have a declarative DSL).

Extending the Boo language itself is probably the most powerful way to add additional functionality to a DSL, but it’s also the most difficult. You need to understand how the compiler works, to some extent. Boo was built to allow that, but it’s usually easier to extend a DSL by adding to the API than by extending the Boo language. When you need to extend Boo to enrich your DSL, those extensions will also reside in the engine and will be managed by it.

The API is part of the DSL. Repeat that a few times in your head. The API is part of the DSL because it composes a significant part of the language that you use to communicate intent.

Having a clear API, one that reflects the domain you’re working in, will make building a DSL much easier. In fact, the process of writing a DSL is similar to the process of fleshing out a domain model or ubiquitous language in domain-driven design. Like the domain itself, the DSL should evolve with your understanding of the domain and the requirements of the application.

DSLs and domain-driven design are often seen together, for that matter.

Use iterative design for your DSLs

When sitting down to design a DSL, I take one of two approaches. Either I let it grow organically, as new needs arise, or I try to think about the core scenarios that I need to handle, and decide what I want the language to look like.

There are advantages to both approaches. The first approach is the one I generally use when I am building a language for myself, because I already have a fairly good idea what kind of a language I want.

I use the second approach if I’m building a DSL for general consumption, particularly to be used by non-developers. This isn’t to say you need to spend weeks and months designing a DSL. I still very much favor the iterative approach, but you should seek additional input before you start committing to a language’s syntax. Hopefully, this input will come from the expected audience of the DSL, which can help guide you toward a language that’s well suited for their needs. Then, once you start, assume that you’ll not be able to deliver the best result in the first few tries.

If you build a DSL when you’re just starting to understand the domain, and you neglect to maintain it as your understanding of the domain and its needs grows, it will sulk and refuse to cooperate. It will no longer allow you to easily express your intent, but rather will force you to awkwardly specify your intentions.

  •  The drive toward DSLs : Choosing between imperative and declarative DSLs
  •  Visual Studio Team System 2008 : Creating new report (part 2)
  •  Visual Studio Team System 2008 : Creating new report (part 1) - Report server project
  •  Visual Studio Team System 2008 : TFS reports for testing - Bugs
  •  Extra Network Hardware Round-Up (Part 3)
  •  Extra Network Hardware Round-Up (Part 2) - NAS Drives, Media Center Extenders & Games Consoles
  •  Extra Network Hardware Round-Up (Part 1)
  •  Networking Jargon Explained (Part 2)
  •  Networking Jargon Explained (Part 1)
  •  The Micro Revolution
  •  Computing Yourself Fit (Part 4)
  •  Computing Yourself Fit (Part 3)
  •  Computing Yourself Fit (Part 2)
  •  Computing Yourself Fit (Part 1)
  •  Touch Interaction - Multi-Touch: An Evolution
  •  Think the Brighter Side to Piracy
  •  These Companies Would Still Be Here In 5 Years
  •  Build Up Your Dream House with PC (Part 4)
  •  Build Up Your Dream House with PC (Part 3)
  •  Build Up Your Dream House with PC (Part 2)
    Top 10
    Windows Server 2003 : Managing and Implementing Disaster Recovery - Fundamentals of Backup
    Microsoft .NET : Design Principles and Patterns - From Principles to Patterns (part 2)
    Microsoft .NET : Design Principles and Patterns - From Principles to Patterns (part 1)
    Brother MFC-J4510DW - An Innovative All-In-One A3 Printer
    Computer Planet I7 Extreme Gaming PC
    All We Need To Know About Green Computing (Part 4)
    All We Need To Know About Green Computing (Part 3)
    All We Need To Know About Green Computing (Part 2)
    All We Need To Know About Green Computing (Part 1)
    Master Black-White Copying
    Most View
    Programming WCF Services : Queued Services - Transactions
    Managing Exchange Server 2010 : The Exchange Management Console
    The 50 Best Headphones You Can Buy (Part 4)
    Play It Smart (Part 2) - Western Digital WD TV Live, Apple TV, D-Link Boxee Box
    All in One - The iDevice To Rule Them All
    Understanding Network Access Protection (NAP) in Windows Server 2008 R2
    WCF Services : Data Contract - Hierarchy
    Building LOB Applications : Databinding in XAML
    Windows System Programming : File Pointers & Getting the File Size
    OS X Mountain Lion: What’s New - The System (Part 4)
    Exploring the T-SQL Enhancements in SQL Server 2005 : Exception Handling in Transactions
    Amiga – Amiga on the web
    Personalizing Windows 8 : Tweaking Your Touch Experience
    Samsung LED Monitor With 170 Degree Viewing Angle
    System Center Configuration Manager 2007 : Developing the Solution Architecture (part 4) - Capacity Planning,Site Boundaries,Roaming
    The ASP.NET AJAX Control Toolkit (part 2) - The Accordion
    Writing 64-Bit Applications for Windows 7 (part 2)
    Create, Read, and Write a Binary File
    Windows Server 2003 : Building a Nameserver (part 1) - Enabling Incremental Transfers, Entering A Records into a Zone, Entering and Editing SOA Records
    BenQ Joybee GP2 Mini Projector - Compact HD-ready projection