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.

Wiki page that shows you how to write a case construct with Smalltalk's surprisingly versatile syntax. Here's what it looks like:

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

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Is the Case abject a monad?

I'm just starting with monads (Or is it monadic programming?), but the implementation of the Case object looks to follow the same style as other monadic types I've seen.

Is this observation correct?

I don't think so..

because in Smalltalk the code:
foo bar; baz
means
(begin (bar foo) (baz foo))
and not
(baz (bar foo))
and according to my very shaking understanding the latter would have made it the state monad.

If I have misunderstood monads then please send complaints to Dave Herman who taught me about them :-)

Yes and No

You're right, it's not the typical monad control operator bind, but it is the special case >> one (I hate how Haskell speaks in line noise). This evaluates the first argument then the second, ignoring the result of the first.

Nit

Most Haskell programmers would use do-notation where 'a >> b' would become 'do a; b' (or laid out vertically with no ';'). I find this hard to consider "line noise". Personally, I find typical Haskell code to contain much less non-alphanumeric characters than say C-style code, but that is just my opinion.

Pronouncing ">>"

">>" is pronounced "then".

the other approaches

It is a good example of Smalltalk's surprisingly versatile syntax. It isn't what Smalltalkers usually do-

However I recommend using the other approaches:

  • use polymorphism instead of a case statement
  • use a dictionary look-up
  • use return as break in an if-statement list

in almost all cases and never to load this utility without a specific requirement.

You know, of course, that the

You know, of course, that there are those that argue that if you use if statements instead of dynamic dispatching, you are not faithful to the OOP doctrine...

If considered harmful


If considered harmful


I'd really like to see predicate dispatch implemented more widely.

OO uber-zealotry considered silly

There are many types of polymorphism in the world:

* Internal vs External
* Parametric (generic) vs Subtype ("inheritance") vs Ad-Hoc (explicit enumeration)

Others may construct other taxonomies on the subject; I consider Luca Cardelli's On Understanding Types to be an excellent resource.

At any rate, the internal/external and parametric/subtype/ad-hoc axes are orthogonal; meaningful examples of each kind exist:

Overridding of inherited virtual functions (or abstract function specficiations--the bread and butter of OO) is internal subtype polymorphism.

Multi-Methods, ala CLOS or Dylan, are an example of external subtype polymorphism.

Templates (generics are examples of parametric polymorphism. In C++, Java, and other OO languages with generics, this can be internal; it is generally external in functional languages.

Pattern matching is a common example of internal ad-hoc polymorphism. Function/operator overloading is also ad-hoc polymorhism, and can be internal or external depending on the language (C++, for instance, allows either).

And finally, the procedural control structures (if, case) are time-honored ways of implementing external ad-hoc polymorphism. (Table-oriented programming is another way as well, though nobody other than topmind seems to use it. :)

The OO uber-zealotry that I refer to in the title of this message is the notion (which seems to spring from the Smalltalk community) that internal polymorphism is good, external polymorphism is bad--that any decision-making should be implemented through inheritance and specialization. (The communities of other OO languages--C++ and Java, and even Smalltalk-derived scripting languages like Python or Ruby, seem to have less of an issue with this).

Needless to say, I consider this a questionable stance to take. In many cases, decisions made by software which might be made on the type (or identity) of an object, are made in modules other than where the object is defined--and are only of concern to that particular module or application. 'Tis better, I think, to keep local concerns local, then burden an object definition with every possibly contingency in every possible application or domain the object might be useful in--which might be a very large set.

While internal polymorphism is a great thing to have and use--so is external polymorphism. One thing I like about C++ (despite its numerous flaws) is it doesn't force me into one style or another--I can define templates, overloaded operators, and such either within a class definition, or outside it. Many other languages make this more difficult. (Of course, C++ operator overloading is broken in the presence of subtypes--CLOS style multimethods would be preferable to the C++ implementation, which uses the declared static type of a term rather than the actual type of the object).

Hmm...

Needless to say, I consider this a questionable stance to take. In many cases, decisions made by software which might be made on the type (or identity) of an object, are made in modules other than where the object is defined--and are only of concern to that particular module or application. 'Tis better, I think, to keep local concerns local, then burden an object definition with every possibly contingency in every possible application or domain the object might be useful in--which might be a very large set.

You describe the C++ solution to this. Another solution is to separate the notions of object definition and code locality (temporal locality, too, if you're into that). This is the approach taken by Ruby, for example, and other languages with open classes. The idea, of course, is that those "contingencies" can be implemented both in the local application or domain and in the object definition. I think Pascal Costanza's ContextL addresses this problem in a similar way, and it's a bit more disciplined than Ruby's open classes.

So, really, to try to draw a general point from this... I'm not sure the internal/external metric is fine enough to capture the real design space here.

faithful to the OOP doctrine...

"dynamic dispatching"

That would be "use polymorphism instead of a case statement" :-)

Before we lose perspective, note that in many Smalltalks the ifTrue: ifFalse: ifTrue:ifFalse: messages are optimised away by the compiler. Ordinary Smalltalk code has many ifTrue: statements, and also makes use of double-dispatch.

other approaches

I found another solution in Squeak that could have been written by Joe Armstrong:
withSide: side setTo: value  "return a copy with side set to value"
      ^ self perform: (#(withLeft: withRight: withTop: withBottom: )
                         at: (#(left right top bottom) indexOf: side))
             with: value
And I saw another solution on the web that setup the dictionary as an instance variable which seemed Javaesquely ugly. Can't find the link now.

Helmut Eller used a similar dictionary trick in SLIME that I noted as interesting:

(defconstant +lowtag-symbols+ 
  '(vm:even-fixnum-type vm:function-pointer-type ...)
  "Names of the constants that specify type tags.
The `symbol-value' of each element is a type tag.")

...

  (let* ((lowtag (kernel:get-lowtag object))
         (lowtag-symbol (find lowtag +lowtag-symbols+ : key #'symbol-value)))
     ...)

Dictionary lookup variation

My Smalltalk's rusty, but can't you approach the same sort of effect using a Dictionary?

   switch := Dictionary new.
   switch at: matchCode1 put: [actionCode1].
   switch at: matchCode2 put: [actionCode2].
   ...
   result := (switch at: matchCodeX) value.

Ok, so I don't know how you'd get a default, but essentially the Dictionary can act just as well as a Switch type statement.

Style

This would also make Joe happy in that it is written as inefficiently as possible. That is fine with me and it's also very clear. However it still jars me to look at.

I'm reminded of page 53 in the Tutorial on Good Lisp Style:

Rule of English Translation

To insure that you say what you mean:
1. Start with an English description of the algorithm
2. Write the code from the description
3. Translate the code back to english
4. Compare 3 to 1

"Make a dictionary, add each possible pair to the dictionary, then lookup the right one" sounds a bit too round-about to me.

My favourite slide is the one on Control Abstraction on page 57. I used to keep a printed copy on the wall of my office and I should do this again.