Why is there no switch statement?


Well, various syntaxes have been proposed along the years, but no one was really satisfying.

There are many alternatives:

  • You can use #ifTrue:ifFalse: repeatedly.
  • You can create a dictionary or, in more complex cases, a dictionary of blocks
  • You can use polymorphism

With Presource, you can also use the #condSelect message macro.

Here is an excerpt from the comment to the CondSelectMacro class:

    {"case combiner: consequent."
     a -> [b].
     c -> [d].
     e -> [f]} condSelect

 -> a ifTrue: [b] ifFalse: [c ifTrue: [d] ifFalse: [e ifTrue: [f]]]

The combiner that links each case to its consequent, "->" in the above, can be anything in `branches', a dictionary you can get by sending #branches.

Python also has no "case" statement, and there is a lot of discussion about this:


As far as syntax, my preferred case/switch statements have been from Tcl (power and expressiveness of case matching with variables being set, etc.) and Erlang. I have no interest in seeing some "C-like" (see also: Java) switch or case statement added to GNU Smalltalk.

One idiom which comes to mind is using arrays of associated blocks. If the LHS block `valueWith: someObject` is not false (or zero), return the value of the second block using `value: someObject value: (result of LHS block)`:

myResult := someObject switchOnBlock: {
  [:x | (x isNil)] -> [:x :y | 0].
  [:x | (x respondsTo: #size) -> [:x :y | (x size)].
  [:x | true] -> [:x :y | 0]

That may not be interesting in terms of "y is always ignored, you fool" but consider (Tcl-motivated):

myResult := someObject switchOnBlock: {
 [:x | (x indexOf: $d)] -> [:x :y | x, ' has a d at ', (y printString)].
 [:x | true] -> [:x :y | x, ' has no d']

Strings could have switchOnRegex which would be quite similar but less pain as LHS "blocks" would simply be regexes.

At least one LHS must evaluate to true, otherwise this would be an error (Erlang-motivated).

I'm quite a newbie to the Smalltalk world; perhaps there are ways of reducing a lot of the syntax when it can be determined (if it can be determined) that a block does not care about the `value:` being sent to it.

You can have zero values for a block:

[x isNil]  "x comes from closure data"

You can also query its arity with #argumentCount.

What you posted is trivial to implement as a normal message; the reason it will not be widely used is that creating all those closures, Associations, and the array is a bunch of overhead to avoid some nested #ifTrue:ifFalse: sends.

All of the overhead relative to writing out the primitive messages manually can be avoided by moving the translation to compile-time, which is what Presource's #condSelect macro does.

#switchOnBlock: isn't quite in the spirit of a ordinary switch. Perhaps you're looking for something like Common Lisp/Scheme CASE?

> #switchOnBlock: isn't quite in the spirit of a ordinary switch

... but I still prefer it to a C-like switch. Anyway, all you want is really to avoid a lot of closing parentheses, not less, not more.

I brought up the Tcl switch statement:


Ignoring the painful aspects, there is a bit more going on than avoiding the nested ifs and closing parentheses with some of the syntax. For example switching with globs and having access to the match data in the executing block is one advantage above simple parentheses avoidance.

Check out my latest blog here for an implementation of Tcl switch. You can tweak the high-level input format to your heart's content by playing with the patterns in #init.

Something like CL:CASE can have stricter semantics than a #condSelect: with a bunch of (x = this) and (x = that) cases, opening the possibility of creative optimizations without reducing the clarity of the code. For example, it could require that for all case values, (x hash = y hash) implies (x = y), and use a dictionary or lookup table stored in the literals for some or all of the values provided, possibly predicated on some kind of threshold (below which a table would only slow down lookup).

User login