Prefix Keywords

Even though we’ve decided not to use smalltalk in OSVM it doesn’t mean that we haven’t been happy with smalltalk ourselves. You always want what you can’t have and if I wasn’t a smalltalk fan before, I was certainly made one by the decision that we were not going to use it anymore. Having said that, I think there are situations where smalltalk is less than perfect; in particular, I find some smalltalk code very hard to read. In this post I’ll discuss some examples of this and describe a neat solution (at least I think so) I once experimented with.

One thing that can make smalltalk code hard to read is the fact that almost everything is a message send, and that the receiver of a message send is always written first. For instance, in a send to ifTrue:ifFalse: you always write the condition first because that is the receiver of the send:

self size ~= other size ifTrue: [ ... ] ifFalse: [ ... ]

When you read this line you have to read past the self size ~= other size part before you see that what you’re reading is actually the condition of a conditional. In this case the line is short so it’s not a big problem but it does cost me just one or two extra brain cycles when reading the line. I would have saved those if you could write the conditional so that it was immediately clear, when reading from left to right, that it was a conditional. For instance:

if: (self size ~= other size) ...

The problem becomes much worse when you consider the operations whose receiver is a block, for instance some repetition, thread and exception operations, because blocks can be very long. An example is this code snippet taken from Squeak (well, slightly changed):

[
[ | promise |
promise := Processor tallyCPUUsageFor: secs
every: msecs.
tally := promise value.
promise := nil.
self findThePig.
] repeat
] on: Exception do: [
^nil.
]

What’s happening here? The outer block is an exception handler, the body of which repeatedly performs some operation. When you read this code you have to read ahead several lines to understand what each block means. By the way, I have no idea what findThePig does.

Another example, also taken from Squeak, is this:

[
[
newMorph := NetworkTerminalMorph connectTo:
self ipAddress.
WorldState
addDeferredUIMessage:
[newMorph openInStyle: #scaled] fixTemps.
]
on: Error
do: [ :ex |
WorldState addDeferredUIMessage: [
self inform: 'No connection to: ',
self ipAddress,' (',ex printString,')'
] fixTemps
].
] fork

Here you not only have to read way ahead to see that the outer block forks a thread, but you have to look closely at the body of the inner block to spot the on:do: selector which doesn’t stand out at all but is very important in understanding the code. This is only two levels of nesting and if you add another level the code becomes even harder to read.

Back when I wrote a lot of smalltalk I started inserting comments to make code such as this easier to read. For instance, I would insert “do” before a loop and “try” before an exception handler:

"try" [
"do" [ | promise |
promise := Processor tallyCPUUsageFor: secs
every: msecs.
tally := promise value.
promise := nil.
self findThePig.
] repeat
] on: Exception do: [
^nil.
]

Here I don’t have to read ahead because I can see which kind of construct I’m dealing with even before I see the opening bracket. You still have to read to the end to understand exactly how the code is repeated and which exceptions are caught, but my experience is that this gives just enough context that you can read and understand the code in one go, something I couldn’t do with the previous version of the code, without the comments.

Back when we were discussing how to change the language, before we decided to go for a C/C++ style syntax, I suggested something called prefix keywords to make the language slightly more C-like and easier to read. It was never very popular but I still think it’s a pretty decent idea. The suggestion was to allow keyword sends that start with a keyword. You can still write ordinary keyword sends such as:

(...) ifTrue: [...] ifFalse: [...]

but you would also be allowed to write:

if: (...) then: [...] else: [...]

which means sending the if:true:false: message to the first expression, with the two blocks as arguments. You would declare the if:then:else: method on booleans like this (here on True)

if: self then: thenPart else: elsePart
^thenPart value

That way, instead of using comments to mark the beginning of constructs like above, you can use a prefix keyword:

try: [
do: [ | promise |
promise := Processor tallyCPUUsageFor: secs
every: msecs.
tally := promise value.
promise := nil.
self findThePig.
].
] on: Exception do: [
^nil.
]

There’s no problem in parsing it and the semantics is as simple as the three other kinds of sends. It also works pretty well for methods such as acquireDuring:. Consider

mutex acquireDuring: [
... critical section ...
]

compared with

acquire: mutex during: [
... critical section ...
]

I think it’s pretty neat. It does change the flavor of the code somewhat but I think it makes the code much easier to read.

Leave a Reply

Your email address will not be published. Required fields are marked *


*