Squeak Smalltalk ships with an, ahem, mildly controversial feature: a case statement. Case statements usually evoke “but that’s not OO!” from people, usually with good reason: a complicated case statement only gets less understandable as it evolves, while a State implementation’s complexity remains more or less constant. (You can concentrate on only the bit you care about; a nightmare case statement requires you to know about a great deal more code.)
So with my hat firmly in the pragmatist corner (as opposed to the ideologically pure corner, for a change), let’s use our new unification library, and build a pattern matching (really, a unifying) case statement.
We use a case statement like this:
which is to say, #caseOf:otherwise: takes an array of associations of thunks (parameterless anonymous functions/closures). It evaluates the key thunk and compares it to
queryType in this case); if they’re equal (
= returns true) then the value thunk’s evaluated and returned. In the event of no matches, we evaluate the second parameter – also a thunk – and return its value.
Technically you don’t need to use thunks as keys. We evaluate a thunk by sending it the
#value message, and since
Object has a
#value implementation (returning
self), we could just use integer or string literals (or anything else that responds to
#value). So why do we use thunks? Because we’re lazy. To be precise, by using thunks we avoid having to evaluate thunks unnecessarily.
An initial implementation of our new control structure is almost embarassingly simple, and is a near cut-and-paste of the existing #caseOf:otherwise:
=? is the unification operator (a short-hand form of
#unify:). Of course this doesn’t work, because
=? is not a
Boolean operator: unification will result in either a most general unifier (which may be empty!) or fail. So we write instead:
More useful: now you can say
and get the result
'a node'. Of course, you often want to know what that mgu was…
So: for each key thunk, try unify the structure returned by the thunk with
self. If we find no match, we return
aBlock value. If we do,
#cull: either evaluates the
assoc value nullary block, or passes the mgu into the unary block:
Note how the above demonstrates unification rather than pattern matching!
This isn’t a terribly efficient construct: worst-case it’s going to be quadratic (N possible thunk evaluations, with a (slightly super-) linear algorithm on each evaluated thunk). As it stands, the most interesting thing about the construct is that it shows how a lightweight closure syntax allows for interesting possibilities.
Future work might be to turn the construct into a proper object. That object could memoise the thunks, which reduces the time cost to a linear cost. Fancier still, and in the interests of safety, one might implement a compile-time transformation, letting the
Compiler inspect the thunks’ ASTs to disallow certain behaviours, or augment the behaviours. Why, that sounds a lot like a macro. It’s not without precedent: Squeak ships with compile-time transformations of
#future: message sends. That’s work for another day though!
If you’d like to see the code in question, you can install DnsClient thusly: