March 17, 2005

It's about syntax

Martin Fowler has stepped into the fray and written about dynamic and static typing. I'm know he understands it, but he hasn't emphasised the difference I find most significant, the syntax. This has been blogged about quite a few times in recent months, but here's another one anyway.

The interesting dynamic languages don't have much built-in syntax: in Smalltalk it's some punctuation and how to send a message, in Lisp it's some parentheses. The bulk of the day-to-day language is in the standard libraries. What this means is that you write an application by extending the language into your domain and then programming in that new language. For example, in Smalltalk using a boolean looks like:

aBoolean ifTrue: [ doSomething ]

in my application I might write:

aDocument ifIsComplete: [ notifyEveryone ]

Notice the difference? Neither can I. There's one consistent programming model throughout the code. Coming from conventional languages, it takes a while to realise that even Booleans are objects with methods like ifTrue:. This consistency is especially nice when working with collections:

aDocument allNodes do: [aNode| aNode removeText ]

as against:

Enumerator allNodes = doc.getNodes().enumerator();
while (allNodes.hasNext()) {
  ((Node)allNodes.next()).removeText();
}

The first expresses that I want to remove text from all the nodes. The second clutters that intent with type information and, more importantly, makes me think at two levels: objects with methods, and process flow instructions — it's overhead.

It's hard to emphasise enough how much clearer you can be when your code describes the solution, not the implementation of the solution.

Building language dynamically

Where this approach flies is that objects can respond to requests they haven't seen before. This is not a technique to be used all the time, but sometimes it's the right thing to do. Unlike in static languages, in dynamic langauges I can call a method that has not been defined on an object. The usual response is to call a special method (in Smalltalk doesNotUnderstand) that by default will throw an exception. As a programmer, I can provide my own implementation of this method that will attempt to interpret the call.

Here's a small example. In Smalltalk I can say:

aDocument head title set: 'new page'

where head and title are not predefined methods. When a node in a document receives a message it doesn't recognise, it translates that into a search of its child nodes and returns the first one that has the same name as the message. This is invisble to the client code that is navigating the document.

In, say, Java I'd have to predefine a whole set of node types, matching the document schema, to write:

document.getHead().getTitle().set("new page");

To handle a different document structure, I would have to change the navigation code. Alternatively I could write something like:

document.getNode("head").getNode("title").set("new page");

or

document.node("/head/title").set("new page");

which means breaking into another syntax (node names or the path expression). I'm juggling two programming environments, Java and something dynamic based on strings, to get the job done.

Once again, the dynamic nature of a language such as Smalltalk and the consistency of its structure means that I can write code about manipulating document contents, not code about manipulating an implementation of a document.

Posted by stevef at March 17, 2005 12:47 AM
Comments

I don't know how that works in Smalltalk, but the Java code gets worse if you add code for null-safety:

if (document!=null && document.getHead()!=null && document.getHead().getTitle()!=null) {
document.getHead().getTitle().set("new page");
}

Unless you prefer to try-and-catch:

try {
document.getHead().getTitle().set("new page");
} catch (NullPointerException e) {
// Do something clever here
}

Posted by: Rickard at March 20, 2005 9:11 PM

How nice to get a comment that isn't spam...

I was mainly concentrating on the "happy" path to get the point across. In either case, I guess I'd have the helper method throw some kind of meaningful exception, rather than rely on null pointers.

Posted by: Steve at March 20, 2005 10:42 PM

Either null checking approach is ugly, obscuring, and potentially error-prone. A better approach is to use the NullObject pattern:

document.getHead().getTitle().set("new page");

where document would return aninstance of NullHead if it had nothing better:

class NullHead implements Head {
public Title getTitle() {
return new NullTitle();
}
//...
}

class NullTitle implements Title {
public void set(String newTitle) {
}
//...
}

But it's still messed up.. look at the client statement:

document.getHead().getTitle().set("new page");

Dave Thomas calls this a train wreck. I tend to use a more colourful metaphor referring to girls' boarding schools, which I'll spare you at the moment since this isn't my blog. This code has to know how to manipulate the implementation of documents (to reuse a phrase from Steve). How about:

document.setHeadingTitle("new page");

Document::setHeadingTitle(String newTitle) {
heading.setTitle(new Title);
}

Head::setTitle(String newTitle) {
title.set(newTitle);
}

That is the way of Objects.

Dave

Posted by: Dave Astels at March 22, 2005 1:41 PM

You'll have to tell me the one about the boarding school offline...

Yes, I know it's a train wreck, but there domains that work like that. Adding methods to the document for all possible gets/sets would be ridiculous. There's probably a better example out there.

Again, the use of a Null Object in this case would depend on the expected behaviour, since it handles missing nodes silently.

S.

Posted by: Steve at March 22, 2005 2:21 PM