Vassili Bykov has a habit of inspiring me. I'll let you decide if the inspiration is worthwhile. Within the last month, Vassili did a series of posts regarding (among other things), currying in Smalltalk. I got to thinking about this and how I might use it to simplify things. I hit upon something different. The opposite in some ways.
Evaluate the expression
base := BrowserEnvironment new.
((base referencesTo: #numArgs) & (base referencesTo: #value:value:))
openEditor
What we see here is a common pattern. One that has showed up more and more as we have tried to make tools more and more dynamic. It is the pattern of having a block (function) which takes a predefined stack of arguments, but the arguments are optional. If the block takes no arguments, we just evaluate it. If it takes one argument, the API defines what the argument will be (often the reciever). If two, maybe a parameter as the second. You can see different variants by varying the number of #value: keywords in the query. It shows up lots in tools and in Widgetry.
It looks nearly the same every time. Something like:
drawAction numArgs == 1 ifTrue: [self drawAction value: aGraphicsContext].
drawAction numArgs == 2 ifTrue: [self drawAction value: aGraphicsContext value: pane]
Whether it's 2 options, 1, or maybe 3, it's always some sort of case statement. What if... we move this behavior to BlockClosure? Consider the following three BlockClosure methods.
cull: anObject
^self numArgs = 1
ifTrue: [self value: anObject]
ifFalse: [self value]
cull: anObject cull: bObject
^self numArgs = 2
ifTrue: [self value: anObject value: bObject]
ifFalse: [self cull: anObject]
cull: anObject cull: bObject cull: cObject
^self numArgs = 3
ifTrue: [self value: anObject value: bObject value: cObject]
ifFalse: [self cull: anObject cull: bObject]
Basically the idea is that we make a recursive method family which throws arguments away til it gets down to the right size. Now that we've done this, we have a higher level pattern we can use in all these spots. Consider the implementation of the VisualWorks's Object>> ifNotNil:
ifNotNil: aBlock
^0 == aBlock numArgs
ifTrue: [aBlock value]
ifFalse: [aBlock value: self]
This can now be written simply as:
ifNotNil: aBlock
^aBlock cull: self
And all of the other similiar patterns in the system can be abstracted to follow the same pattern.
Some interesting things fall out of this. If we want, we could optimize these parallel to the value: primitives. If I recall correctly, VSE would have made the dispatch based on varying arg count easy, as it had separate subclasses for the 0,1, and 2 arg blocks. Or maybe it was another Smaltlalk.
Another interesting thing is the single problem that my much loved Symbol>>value: technique encounters becomes easier to solve. It would often be nice to do some ifNotNil: code as:
^(self customer ifNotNil: #name) ifNotNil: #size
But it doesn't work because of the ambiguous meaning of argCount in that context. But we can implement cull: on Symbol the same as value: and suddenly it just magically polymorphically slots into place.
I had thought I would actually simplify AnnouncementDeliveryDestination to use cull:, but while it does the case statement like determination of what to do, it actually determines what to do up front at configuration time and those avoids redoing the computation each time. So I'm still pondering that one.
Michael and I batted many names around for such a behavior. maybe:maybe:maybe:. perhaps:perhaps:perhaps:. There were some others. In the end, credit for this one goes to Martin Kobetic. What would you call it? Terse wins.
Finally, you can find this code publised in the Open Repository as BlockCulling.