Currying, unless you are into Thai cooking, is partial function application commonly used in functional languages. (Some argue it should be called Schönfinkelisation, but oddly so far the term hasn’t caught on). Given a function of N arguments, one can invoke it with K arguments, K < N. This produces a new “curried” function of N – K arguments, with the K arguments of the original call closed over (remembered).
In Smalltalk, currying would allow the example:
| add inc | add := [:a :b | a + b]. inc := add value: 1. (inc value: 2) + (inc value: 3)
run without errors and produce 7. This feels like a significant change down in the foundations of the universe, but amazingly it is trivial to implement.
Both in VisualWorks and Squeak, when a Block is invoked with a wrong number of arguments, message processing ends with a primitive failure in the
valueWithArguments: method. The standard Smalltalk response to that is to complain about the wrong number of arguments. All we need is change that response to curry the receiver when appropriate.
To make the example shorter and nicer (we’ll see why a little later), we define a class called CurriedBlock, with instance variables
arguments and an obvious initialization method
block:arguments:. We also change the standard
valueWithArguments: as follows:
valueWithArguments: anArray <primitive: 500> (anArray isMemberOf: Array) ifTrue: [anArray size < self numArgs ifTrue: [^CurriedBlock new block: self arguments: anArray] ifFalse: [self error: 'Incorrect number of arguments']] ifFalse: [self error: 'valueWithArguments: requires an Array']
The method above is a simplification of the VisualWorks version, with the horrible UserMessages replaced by readable strings. The Squeak version is nearly identical.
CurriedBlock needs to reproduce the standard block protocol:
valueWithArguments: anArray ^block valueWithArguments: arguments, anArray value: anObject ^block valueWithArguments: (arguments copyWith: anObject) ... other required value:... methods numArgs ^block numArgs - arguments size
This is all that is needed to make the example work.
In fact, we could do without the CurriedBlock class. Our implementation of
valueWithArguments: could answer a real block by doing something like:
valueWithArguments: anArray ... ifTrue: [anArray size < self numArgs ifTrue: [^self curriedWithArguments: anArray] ... curriedWithArguments: anArray | arity | arity := self numArgs - anArray size. arity = 1 ifTrue: [^[:arg | self valueWithArguments: (anArray copyWith: arg)]]. arity = 2 ifTrue: ...
However, since Smalltalk blocks don’t support the equivalent of Lisp “rest” arguments, the
curriedWithArguments: method would have to explicitly enumerate and handle all arities we realistically expect to use in block invocations. Using CurriedBlock instead produces a nicer example.
To be continued.