Navbar

Categories

Pages

Archives

Ruby, Lisp, and DSLs

I’d been exposed to Ruby-as-DSL before, but I never quite understood what it meant in terms of how it might impact how you’d actually write code.

For instance, I spent some time reading Ruby Best Practices, and in particular there’s a chapter about Designing Beautiful APIs. They don’t use the term DSL as far as I can tell, but that’s essentially what they’re doing when they have examples like this one (which I’m paraphrasing):

server.rundo
handle "/foo"{"bar"}
handle "/baz/quux"{"frotz"}end

The details aren’t important. What matters is that you can see something a lot like a DSL. Here’s another example, lifted right from the docs for a Ruby DSL for the DOM, called Arbre:

Rake is another example of a DSL. The list goes on and on; it’s a common design pattern in Ruby, and as I see it now, that’s for a very good reason. But I didn’t realize it.

The problem was that I missed the forest for the trees. I’d heard it mentioned before, that objects are their own DSL. And I believed it, albeit in some trivial way. To be specific, if you were to de-sugar the Ruby code above, it would look like normal code. The “language” is just a set of methods; the do call is just a series of calls to those methods in the context of the object. As stated that seems unremarkable to me. Every OO language has methods!

Here is one of the rare cases, though, where syntax helps call out semantics in a really interesting way. When you call ul do, you are inside an object, and you have privileged access to a set of methods. Instead of laboriously writing out the variable name with method calls, you’re invoking methods which themselves return methods, inside which you can continue to access the DSL’s features. It’s pretty great!

Now that I’ve been exposed to Lisp macros as well as Haskell, a lot more of this makes sense. The Arbre example above sure looks a heck of a lot like do notation from Haskell or do from Clojure. It only works if you squint, of course; those li calls are presumably operating on state within ul, appending to some internal representation of the DOM or what have you.

All in all, it’s very impressive, but I am not sure whether I would call it profound. It looks really nice, given Ruby’s spare syntax for function/method invocation. It’s amazingly expressive. But your “language” as such is bound up in objects, it executes within the context of an object, and under the covers, in just about every way, it is equivalent to a series of method calls. (I could be wrong about this, of course!)

Lisp macros

I always thought it was a little goofy or abstract to put it as “programs writing programs,” which is how I often heard macros in Lisp described. True, this is a practical definition of what you’re doing when you use macros; it’s not inaccurate or misleading to say this. I think it does simultaneously understate and overstate it to someone who is uninitiated, however.

It’s overstating it in that you’re not writing complete programs with a handful of lines. Nobody really thinks that’s possible, and that triggers skepticism. It’s understating it, though, in that it fails to capture the implications of being able to design your own language.

In reality, you’re doing exactly what the Ruby people above are doing. When creating a DOM is as trivial as calling functions which have the same name as their elements, you’ve just created your own language. You’re operating one level above the problem, allowing you to deal with it using a language specific to your problem domain (ding ding ding).

Another thing about Lisp macros that I never quite got is that they’re really nothing like macros in the likes of C. The C preprocessor, as far as I know, is mostly orthogonal to the language. I don’t mean to argue that this means it’s irrelevant. Rather, it’s a glorified copy-paste engine, and as such knows next to nothing about C syntax or semantics.

Lisp macros are not like this. Lisp macros, as I understand them, are first class by any reasonable definition of the term. Even in Ruby, you’re still just calling methods as anyone else would.

However, at least in Clojure, there are plenty of “language features” which are themselves implemented as macros. I used quotes there to make a point: if your code can operate at the same level of sophistication or privilege as many ostensibly core language features, what implications does that have for your programs?

Lisp is a general purpose programming language with the ability to customize itself, at a very basic level, to create new semantic constructs appropriate to your problem. People say that a lot and I feel I’m only just grasping the implications. It seems far more profound.

The irony that prompted this post was my exposure to Arbre, and how it brought all of this home. I gained some newfound respect for Ruby, and I got yet another glimmer of what you can do when the language encourages you to mold it to your needs.