Friday, February 2, 2007

A case of case

In all the documentation I have seen on Smalltalk, everyone always points out how to use #ifTrue:ifFalse and why it works this way. Ok, got it. But at some point, one needs more branching control then just if/else.

Now I know; anytime you branch you could accomplish the same thing using method overriding [1], but it isn't always practicle to break out a new class for a one time thing. This is the reason there is a #ifTrue:ifFalse in the first place, no doubt.

But what we can learn from #ifTrue:ifFalse is, if there is something the language doesn't provide that you need, just put it in. :) I was writting a complicated function some time back where I ran into just this case. I was having to nest if statements, but the problem was, each condition was complex. So several of the conditions had to be repeated in different if statements. Obvious code smell. "If only I Smalltalk had case!" I thought. Wait a minute, why don't I just write one? I didn't handle the general case, I just made a method that handled my specific case.

onFirstComplexCase: aFirstBlock orSecond: aSecondBlock else: aLastBlock

Of course using this method instantly made a 20 line [2], complicated and thus, unclear method into a short, very readable one. And then today, while tracking something else in the Object class I found that someone else thought of this first and did the more general solution. :)

caseOf: anAssociationCollection otherwise: aFailBlock

anAssociationCollection associationsDo: [:assoc|
assoc key value = self ifTrue: [ ^ assoc value ]
].
aFailBlock value.

Very nice. This lets us do something like:

event
caseOf: {
[ #clicked ] -> [ self handleClick ].
[ #resized ] -> [ self handleResize ].
" ... "
}
otherwise: [ self error: 'unknown event: ', event asString ]

Very cool. It looks like an ML style caseOf statement. But this still doesn't handle my case does it? My cases are much more complicated then just comparing a class to something. You have, no doubt, figured this out, but just in case; how does the case method work again? It evaluates the key and compares that result to the recieving object. So if we have a more complicated case we could just go:

true
caseOf: {
[ self hasThisTrait and: [ Date today month = 12 ] ] -> [ self goCrazy ]
" ... "
}
otherwise: [ self error: 'logic error' ]

[1] An if statement basically creates a branch in execution. This can also be done with a class hierarchy and method overloading (and in fact this is how the boolean hierarchy works in smalltalk).

[2] In Smalltalk, if your method gets over 5-10 lines it is time to start looking at some refactoring. I didn't completely believe them initially when they told me, but so far it has been the case nearly every time.

1 comment:

Antony Blakey said...

Since I discovered

true caseOf: {
...
}
otherwise: [ ... ]

I use it 99% of the time that I would have previously used nested IfTrue:ifFalse:. IMO it makes logic much easier to parse.