Keywords, Magic and (E)DSLs

Imagine you could write programs with text like this:
aJanitor open: aDoor with: aKey.
Pretty sweet. How might the implementation of open:with: look? Like this:
self insert: aKey into: aDoor; turn: aKey; push: aDoor.
Imagine how productive you could be if you could write code this way. Imagine how easy it would be to maintain code written this way. It's so clear what this code achieves (though maybe not quite how it works) that I'm not even going to explain it. This is Smalltalk.

A big part of the reason why Smalltalk is so great is this keyword message syntax (blocks help a lot, too, as we shall see). It allows code like the above, which look very much like natural language. If you are more familiar with curly bracket languages or similar, it can take a little effort to get used to reading code like this, but it's worth it.


No Magic

There's more to it than just ease or reading and writing, though. Smalltalk has no magic. Well actually, it has a lot of magic, but it enables all programmers to be magicians, whereas certain other languages hide their magic away. The language implementers grant themselves wizardly status, but deny it to the language user.

One kind of magic is a little bit of laziness around choosing alternatives. In Java we choose between two alternatives like this:
if(condition){
this.doOneThing();
} else {
this.doAnotherThing();
}
and we can be confident that one thing or the other will happen, but not both. That's actually quite clever, although the order that Java is usually taught in hides this. Imagine that if were a method on some object...ah yes, turns out that Java isn't really object oriented after all. Java has objects that instantiate classes that declare methods, it's true, but the code inside those methods, with if and switch and the rest, isn't object-oriented. Such a shame, as we shall see.

Anyway, imagine that Java were an OO language and that if, therefore, were a method. There would be a potential problem (assuming the near universal eager evaluation of function arguments). Our imagined ObjectJava code might look like this:
if(condition, {doOneThing();}, {doAnotherThing();});
which kind-of suggests that both the one thing and the other would get done. And this is the magic of the keyword if and it's funny syntax. What are those things with the {}'s? They're little lumps of code (possibly with local variables declared in them), and one of them gets run and one doesn't, under programmatic control. That's a pretty powerful feature, too powerful for the Java wizards to allow we working programmers to wield.

The syntax of our invented ObjectJava is pretty bad there, );}); isn't a thing of beauty, although it's not much worse than the way some real Java looks. The Smalltalk equivalent is much neater. Continuing with our janitorial example:
aDoor isAlarmed ifTrue: [self disarm: aDoor] ifFalse: [self openNormally: aDoor].
The [], conspicuously unlike the {} of Java, creates a new object, a so-called block, which contains the code to run. And the interesting thing about this is that ifTrue:ifFalse: is just a method. It's a method of the class Boolean. And Boolean has two subclasses: True and False, each of which is a Singleton. The sole instance of True is called true, and similarly for False.

The implementation of ifTrue:ifFalse: in True is(+):
ifTrue: t ifFalse: f
^ t value
and in False it is
ifTrue: t ifFalse: f
^ f value
(Note: ^ is how Smalltalk spells "return" and value is a method on blocks that returns the value of the code inside the block).

This is pretty much exactly the implementation of Booleans used in the Lambda Calculus, and that fact reveals one aspect of the close relationship between OO and functional programming.


(Embedded) Domain Specific Languages

Perhaps most astonishing about the Smalltalk approach is that Boolean values and selecting different actions based upon them is not part of the language. These are facilities provided by methods of classes in the Smalltalk standard library! This library (the "standard image") turns out to contain a large number of overlapping Embedded Domain Specific Languages--one of which, provided by the classes Boolean, True and False is specific to the domain of two-valued logic. That's a very remarkable thing. Most remarkable is what it says about the business of writing programs in Smalltalk.
There is no mechanism available to the Smalltalk programmer to create programs other than the creation of EDSLs
Even the Smalltalk programmers who write Smalltalk itself don't have any other (*) mechanisms available to them. And that's the benefit of No Magic: anything the language implementers can do, you can do too. And you end up doing what they language implementers do, that is, implement (a) language(s). Our example,
self insert: aKey into: aDoor; turn: aKey; push: aDoor.
is nothing more (and emphatically nothing less) than a statement about janitors, doors and keys written in an EDSL that knows about...janitors, doors and keys.


Dot Dispatch language

What about those of is who are not fortunate enough to be able to use Smalltalk in our work. What can be done in the languages where regular programmers are second-class citizens?

The best example that I've seen of an EDSL in Java is the language used to describe expectations in jMock. This language supports a certain style of programming that many folks are finding valuable. jMock allows us to write programs to talk about how other programs will behave. Like this:
mock.expects(once()).method("m").with( or(stringContains("hello"),
stringContains("howdy")) );
again, I think this is clear enough without explanation. You would use code like this within a programmer test to ensure that the test failed if some other object collaborating with our mock doens't call into the mock in the expected way.
It's worth digging into the implementation of jMock, not only because it is delightful code, but also to see the amount of heavy lifting that has to go on behind the scenes to make it possible for Java programmers to write code of the clarity seen above.


Productivity?

Building DSLs is the bread and butter of Smalltalk (and Lisp) programming, but is a bit of a struggle in the Java (and similar) worlds. The big vendors are attempting to fix this through the use of mighty tools in the interests of supporting a new-but-old-but-new model of development, a rather fishy proposition at best.

This is symptomatic of one way in which the industry has decayed. The message of Smalltalk (and Lisp) is that the route to productivity is to use simple tools with few features and allow everyone interested to build upon them. The favoured route at the moment is to encode every good idea into an all-singing all-dancing "solution", take it or leave it.

Once, the computer itself was locked away, ministered to by a priestly class who mediated your desire to perform computation. Then the (personal) computer revolution began to start and we could all gain direct access to our computing power, and grow our own way of using it. But the something went wrong and a new priestly class--the tool builders in the corporate software vendors--arose to try and put the genie back in the bottle (to mix an increasingly muddled metaphor). They must not be permitted to succeed.

(+) Well, kinda. If it were, then it would be, but for practical reasons it isn't. But for our purposes, it's exactly as if it is. Don't worry about it.

(*) Well, they only have the one: they can hack the virtual machine itself. But then so can you

3 comments:

Anonymous said...

Good article. 'Language-based programming' (creating DSLs) seems to be experiencing a revival. The creations of DSLs is a big topic in the Ruby community at this point. RubyOnRails is essentially a DSL for web development (in addition to lots of code generation).

marick said...

I'd emphasize keyword arguments more than Boolean ifTrue:ifFalse:. The latter is clever, but I don't think it really adds all that much clarity/power over just having lambdas in the language. Keyword arguments make a huge difference in readability, though. (Although I have a slight preference for making them order-independent and optional, as they are in Common Lisp, rather than making them part of the message name, as in Smalltalk and Objective-C.)

You mentioned keyword arguments, but you didn't explicitly point out an additional nice Smalltalk feature: cascades (I forget what the real term is). For any method call, you can (in effect) make the return value of the method be the object it was sent to, rather than whatever the real return value is. That means you get to decide what it makes sense to chain together, rather than have the designer decide for you. (Actually, I don't know if it's really used that much, since I'm not a Smalltalk programmer, but I've always thought it was clever in a practical way, whereas ifTrue: is clever in a Lisp hacker kind of way.)

Steve Freeman said...

The jMock (now version 1) syntax has clear roots in Smalltalk. It originally started as multiple calls on the same object, like a cascade, because I was tired of typing in:

object.setA(a);
object.setB(b);
// ho hum

then it just kinda took off.

It turns out that interface chaining has a very similar feel to keyword arguments. In fact we had a party trick where we turned of the punctuation in the IDE and the text turned into Smalltalk (well, nearly)

mock expects once method "m" with anyOf stringContains "hello", stringContains "howdy"