There’s so much talk lately about Ruby being so great for writing domain specific languages, but this is only partially true, Ruby’s not all it’s cracked up to be. Too much hype and not enough substance, if one looks a little deeper.
Every programmer knows one domain specific languages that is so common, it’s built into most languages, predicate logic. The good old if, if/else, and while forms, and several variations of them like do, unless, etc. So please forgive me while I state the obvious. Each form works using true/false as the input, and a block of code to be conditionally or repeatedly executed depending upon the value of the input. In a C’ish form, these look like so…
if(aBooleanCondition){
someCodeToCall();
} else {
someOtherCodeToCall();
}
while(aBooleanCondition){
someCodeToCall();
}
There’s nothing new here, every programmer knows this. What’s interesting is that Ruby, like most languages, uses special forms for predicate logic. If Ruby is so great at writing domain specific languages, why not implement the most common domain specific language, predicates, just like any other embedded domain specific language?
This might sound crazy, but there’s no reason that if/else and while need be part of the language, they can be moved into the library, given a language sufficiently powerful enough. When I first learned Smalltalk, the piece of code that blew my mind the most, was exactly this. Smalltalk the language, has no if/else or while statement, instead the domain specific language of predicates is implemented with simple objects and method calls, no compiler voodoo “required” (this is actually optimized by the compiler, I speaking conceptually here). Here’s the most interesting bits.
True>>ifFalse: alternativeBlock
^nil
True>>ifFalse: falseAlternativeBlock ifTrue: trueAlternativeBlock
^trueAlternativeBlock value
True>>ifTrue: alternativeBlock
^alternativeBlock value
True>>ifTrue: trueAlternativeBlock ifFalse: falseAlternativeBlock
^trueAlternativeBlock value
False>>ifFalse: alternativeBlock
^alternativeBlock value
False>>ifFalse: falseAlternativeBlock ifTrue: trueAlternativeBlock
^falseAlternativeBlock value
False>>ifTrue: alternativeBlock
^nil
False>>ifTrue: trueAlternativeBlock ifFalse: falseAlternativeBlock
^falseAlternativeBlock value
BlockContext>>whileTrue: aBlock
^ [self value] whileTrue: [aBlock value]
And here’s how this DSL is used in Smalltalk…
aBooleanCondition
ifTrue: [ self someCodeToCall ]
ifFalse: [ self someOtherCodeToCall ]
[aBooleanCondition] whileTrue: [ self someCodeToCall ]
The main thing to note here, beside the beauty of Smalltalk, is that blocks, objects, and methods calls are the necessary building blocks of “language” itself, any language. Having a succinct notation for the delayed evaluation of code, and allowing that code to be placed into variables and passed around, is all one needs to build virtually any domain specific language they can dream up.
Ruby at first glance, appears to have this trait. Ruby has blocks, but it didn’t do them right. Ruby introduced an ugly hack called yield for pretending to pass a block with a terse form {}, but it only works for the last argument to a method, you can’t pass two blocks to a method with this same terse syntax. The above predicate domain specific language can’t be implemented in Ruby in exactly the same way, because Ruby can’t pass both blocks cleanly, and even has two forms for specifying a block, the short and sweet { } and the longer “do end”. This simple case works…
aBooleanCondition.ifTrue { someCodeToCall }
But the two block case won’t, at least not in a single method call, you’d have to chain two calls together.
aBooleanCondition
.ifTrue { someCodeToCall }
.ifFalse { self someOtherCodeToCall }
Having keyword message syntax, is another vital piece of the puzzle. You see Ruby code faking this all the time by passing around associations to avoid the ugliness of positional nameless arguments, or as above, chaining multiple call together to fake the keyword style. But this doesn’t look like idiomatic Ruby, and Ruby won’t allow you to build forms that look like the built in if/else/while.
Rather than suffer this ugliness, Ruby simply provided a procedural syntax for predicate logic, with reserved words at the language level. Ruby, as nice as it is, is still a language that “can” build limited domain specific languages, but not necessarily with a syntax on par with what the language itself provides.
Smalltalk seems much more a notation, that can do nothing but build domain specific languages. Ruby isn’t object oriented at the level Smalltalk is, is still falls back to procedural constructs and special syntax for many things. Smalltalk, is pure, objects all the way down, at every level, even the simplest and most common domain specific language of all, predicate logic. When you create a domain specific language in Smalltalk, your code never looks different than code provided by the compiler writer himself, it’s one syntax to rule them all.
Smalltalk shares this trait with languages such as Lisp and Scheme, truly growable languages that put you, the programmer, in charge of what language you want, not some compiler writer who might take six more years to add some feature you just got to have now. When you need a new language feature in Smalltalk, you simply add it. It’s a feeling that once you’ve become accustomed to, you can’t live without, and one you don’t quite get with Ruby.
There’s been a lot of fuss lately about the next Ruby not supporting continuations, a feature vital in building a framework as elegant as Seaside. Yet, continuations were added to Squeak in about 39 lines of code, no big fuss necessary. This is that feeling I’m talking about, no waiting for the next official VM to support them, it’s just a Smalltalk class, like any other.
Ruby is terse, nice looking, and even massively productive, but elegant it is not. And maybe that doesn’t matter to most, but for those like me, elegance is real power, power that Ruby doesn’t have. Ruby’s an interesting sign post on the way from mainstream languages like Java and C#, but it’s not the destination, it’s just a good warm up for learning Smalltalk.
Related Link: Register Domain Name Instantly Register Domain Names Instantly with $6 USD and Flat Registration Fee for next years
Tags: Lisp, Ruby, Seaside, Smalltalk