Static Verification: An External DSL Advantage

Last week I wrote a number of posts about web development "languages" developed as internal DSLs. An internal DSL is a set of libraries written on top of a host language that, through the use of meta programming facilities, looks a lot like a domain-specific language. I looked at Ruby on Rails as a prime example of an internal DSL built on a dynamic language, at JBoss Seam as a framework built on Java, and then at Lift and my own little page language as examples of internal DSLs in Scala.

This order was on purpose. I started with the most dynamic, catch-any-error-at-runtime language and ended with the most strongly typed we-try-to-find-any-mistake-at-compile-time solution. I've been accused of bashing these languages just for the heck of it, but there was a point to this, and it's more than promoting WebDSL.

But let's talk a little about WebDSL anyway. WebDSL is an external DSL for developing data-rich web applications. "External DSL" means that it is not built on top of an existing language like Ruby or Java, but that it has its own syntax and compiler. Developing such an external DSL is typically more work than developing an internal DSL, which is a disadvantage, but external DSLs have their advantages too.

Whenever we present WebDSL to people that have done some web development before, we get asked "Why is this better than Rails? Why not an internal DSL?" Although we always have problems answering this question, because none of us are very experience Rails developers, our answer comes down to a few general things that potentially apply to any external DSL:

  1. We have a nicer syntax, as an external DSL we're not limited to encoding things in a Ruby or Scala way
  2. Being platform independent, we can generate Java, PHP, Python and C# code if we want to
  3. An external DSL compiler can statically analyze your application to find common programmer mistakes

There's also the runtime performance issue, but I don't want to get into that.

DSLs built on modern languages like Ruby and Scala look pretty natural. But from time to time you gotta wonder, why do I have to put a colon in front of this word, why do I have to use quotes here? Because you are building on top of a host language, you have to obey that language's syntax and evaluation rules, whether you like it or not. External DSLs do not have this restriction, they define their own syntax and interpretation of that syntax. 

The second advantage is clear, our language does not rely on any platform in particular and we could therefore generate code for any web platform. With Ruby on Rails you're limited to, well, Ruby. Admittedly, there are Ruby implementations for the JVM and .NET now, but what about my little brother who can only afford cheap PHP hosting, should he just use... PHP?

We felt that the current state of the art in web frameworks is not very resilient to programmer mistakes. Typos lead to enormous stack traces and are often difficult to track down, right? That's the thing I tried to verify last week.

It turned out we were pretty right. Although not everybody agrees.

Static verification of external DSLs

WebDSL is a statically typed language. It consists of a number of sub-languages. Currently we have a language for defining user interfaces, data models, business logic, access control, data validation and workflows.

To get a flavor of it, here's an example entity definition. It defines a task entity:

entity Task {
  name        :: String
  description :: Text
  done        :: Bool
  archived    :: Bool
  user        -> User (inverse=User.tasks)
}

and a page definition:
 
define page tasks(user : User) {
  section {
    header{"Tasks for " output(user.username) }
    table{
      for(task : Task in user.todo) {
        row{ 
          output(task.done)
          output(task)
        }
      }
    }
  }
}
 
and an access control rule that matches any page whose name starts with edit.
 
rule page edit*(*) {
  loggedIn

The nice thing is that we can detect all kinds of mistakes in WebDSL code at compile time. For instance, what if the inverse property of user in the Task entity does not exist?

$ webdsl build
Loading application settings (application.ini)...
[ webdslc | info ] stage 1: parsing webtasks.app
[ webdslc | info ] stage 2: importing modules
[ webdslc | info ] stage 3: typechecking
* webtasks.app:21/25: error: The field User.tasks does not exist
inverse = User.tasks

It points to exactly the right file, line number and column number. Or let's say we misspell a property name in a page definition:

for(task : Task in user.todo) {
  row{ 
    output(task.don)
    output(task)
  }
} 

Result:

* webtasks.app:119/15: error: No property don defined for Task
task.don

Or, mistype a template name:

for(task : Task in user.todo) {
  row{ 
    outputt(task.done)
    output(task)
  }
} 

 

The error we get at compile time is as follows:

 

* webtasks.app:39/14: error: Template with this signature not defined 
outputt(task.done)

Note the use of domain-speak. This template is not defined, not value or method, but template.

 

What if we define an access control rule for a page, or set of pages that does not exist:

Access control warning: unused rules: 
  rule page edit*(*)

We are currently working to also add more thorough checks for page element compositions. For instance, are rows defined within tables, listitems within lists and so on. We're also working on IDE integration, so that your WebDSL programs are checked as you type.

No matter if you like the WebDSL language and its syntax or not, love or hate the formatting and wording of its error messages, abstract from all of that and the opportunity is clear:

An external DSL compiler can detect mistakes early and its error messages can be very clear and domain-specific.

Because we're building our own syntax, compilers and typecheckers it is quite easy to add checks like these. Detecting mistakes in this manner is either not feasible, or at the very least more difficult in internal DSLs. In dynamically typed languages it's extremely hard because of the language's dynamic nature, in statically typed languages like Scala it may require building a compiler extension, which is also a non-trivial exercise.

Although you may say you would never make mistakes that are caught by checks like these, they have turned out to be extremely useful to us, as WebDSL users ourselves. But then again, that may be because we're just all a bunch of NNPPs.