Edit Rename Upload Download Back to Top

Smalltalk case-statement

When the tutor said that Smalltalk has no case-statement, he meant it has no case-statement in the base class library. Smalltalk is infinitely extensible and several people have written case-statement add-ons. None of these have ever been made part of the base class library because Smalltalkers find the above three approaches adequate for all cases. (Thus, unlike Smalltalk's if-statement, no case statement has ever been optimised in commercial Smalltalk compilers and so they are not quite as fast as in other languages.) However, if Bob really wants a case-statement, he can have one that is more flexible than Java's and fully fast enough in most situations.

It's instructive to see how easy it is to write. (I shall present Paul Baumann's case-statement construct as it is, I think, hard to find on the web. Other case-statement utilities for Smalltalk have been written by Peter van Rooijen and by a Squeaker.) We know that curly-bracket-language:

    if (aBoolean)
	then {some_code}
	else {other_code}

becomes

    aBoolean
	ifTrue: [someCode]
	ifFalse: [otherCode]

in Smalltalk. It's fairly obvious, therefore, that curly-bracket language:

    switch (aValue)
	case {match_code1} : {action_code1; break;}
	case {match_code2} : {action_code2; break;}
	...
	default {other_code;}

needs to become something like

    aValue switch
	case: [matchCode1] then: [actionCode1]
	case: [matchCode2] then: [actionCode2]
	...
	default: [otherCode].

in Smalltalk. This won't quite work as its stands since, unlike the Java-like version, ours has to be built out of existing program elements, not reserved words, so needs to separate the arbitrarily repeated case:then: elements in some standard way. Full-stops are no use:

    aValue switch case: [matchCode1] then: [actionCode1].
    aValue switch case: [matchCode2] then: [actionCode2].
    ...
    aValue switch default: [otherCode].

has no benefit over if-statement lists. Nested brackets are no use:

    ((((...((((aValue switch
	case: [matchCode1] then: [actionCode1])
	case: [matchCode2] then: [actionCode2])
	...
	default: [otherCode]

would send each case:then: to the result of the previous one but it's already obvious that we can write a case-statement as nested ifTrue:IfFalse: statements

    matchValueCode1 ifTrue: [actionCode1] ifFalse: [
    matchValueCode2 ifTrue: [actionCode2] ifFalse: [
    ...
    ifFalse: [otherCode]]]]...]]]]].

and the above gives us just as many brackets to nest. What we need is to send each case:then: in turn to the result of the switch method in a way that needs no brackets. In Smalltalk, that is done by a cascade:

    aValue switch
	case: [matchCode1] then: [actionCode1];
	case: [matchCode2] then: [actionCode2];
	...
	default: [otherCode].

Now we have worked out the case:then: protocol, we can write a simple test. The test falls over because the switch method does not yet exist. It must be understood by any value, i.e. by any object, and must return something that responds to repeated case:then: messages; sounds like switch had better return an instance of a new class, which we may as well call Case.

    Object
	subclass: #Case
	instVars: 'criterion satisfied response'

    Case>>for: anObject
	^self new criterion: anObject

    Object>>switch
	^Case for: self

This makes switch return an instance of class Case that knows:

Our test drives us to give Case these instVars while writing first versions of the case:then: and default methods:

    Case>>case: oneArgTestBlock then: execBlock
	"The oneArgTestBlock must return a Boolean value when passed the criterion of the receiver."

	satisfied ifFalse:
	    [(oneArgTestBlock value: criterion) ifTrue:
		[response := execBlock value.
		satisfied := true]].

    Case>>default: execBlock
	satisfied ifFalse: [response := execBlock value].
	^response

This works and offers possibilities for further development.

	Case>>caseIs: testObject then: execBlock
	    case: [:caseCriterion | testObject = caseCriterion] then: execBlock.

	Case>>caseIsAny: testCollection then: execBlock
	    case: [:caseCriterion | testCollection includes: caseCriterion] then: execBlock.

Adding protocol for drop-through behaviour (i.e. equivalent to omitting break at the end of a case' action code in a curly-brackets language) is a more extensive change. We can do this by adding a case:process: method, implemented like case:then: except it sets satisfied to false, not true, if its condition is met. To make this work, we refrain from initializing satisfied to false (so the system sets its initial value to nil), instead using a

    Case>>isSatisfied
	^satisfied == true

accessor in the case:then: and case:process: methods, and checking satisfied isNil ifTrue: instead of satisfied ifFalse: in the default method.

And there you have it; a case-statement that allows arbitrary matching code and is easy to extend. It runs in any dialect of Smalltalk. See Paul Baumann's article in The Smalltalk Report (August 1997, Vol 6 No 9) for a fuller description.

However I recommend using the other approaches:

in almost all cases and never to load this utility without a specific requirement. Here are some relevant remarks on why not by Andy Bower.
When I first came to Smalltalk from C++, I couldn't understand how a supposedly fully fledged language didn't support a switch/case construct. After all when I first moved up to "structured programming" from BASIC I thought that switch was one of the best things since sliced bread. However, because Smalltalk didn't support a switch I had to look for, and understand, how to overcome this deficiency. The correct answer is, of course, to use polymorphism and to make the objects themselves dispatch to the correct piece of code. Then I realised that it wasn't a "deficiency" at all but Smalltalk was forcing me into much finer grained OOP design than I had got(ten) used to in C++. If there had been a switch statement available it would have taken me a lot longer to learn this or, worse, I might still be programming C++/Java pseudo-object style in Smalltalk.

I would contend that in normal OOP there is no real need for a switch statement. Sometimes, when interfacing to a non-OOP world (like receiving and dispatching WM_XXXX Windows messages that are not objects but just integers), then a switch statement would be useful. In these situations there are alternatives (like dispatching from a Dictionary) and the number of times they crop up doesn't warrant the inclusion of additional syntax.

(The same goes for breaking out of a loop without returning. You just don't need it that much; in my 8 years of programming Smalltalk I can recall having wanted a non-returning break statement construct about twice!) -- Andy Bower


I agree with Andy. Except in very rare cases (e.g. complex parsing applications) and even then mostly just to make the code a little neater, you never need to use the above utility.

(Back to new code syntax.)

(Written by Niall Ross as part of Smalltalk for Java Programmers.)


Edit Rename Upload Download Back to Top