Currying in Smalltalk, part 2

In the first part we changed the behavior of the value: protocol of blocks to support currying. That changes the existing behavior of the system, making it possible for those messages to answer a block instead of a “real” value. Is that a problem?

One could argue that the new behavior comes into force only in the situations where the original system would signal an error anyway, so if it falls over because of getting a block it didn’t expect, it would still have fallen over in the old system because of the number of arguments mismatch. On the other hand, what if it doesn’t fall over immediately after it gets that unexpected block? It is possible for the new behavior to mask an error and make it difficult to find its cause later. Also, some might not like this kind of a change on the familiarity grounds. Gilad was the one who got me on the track of playing with currying, and for these reasons he suggested that it might be a good idea to keep value: and friends intact and introduce a different evaluation message that would allow currying–something like curry:curry:.

A similar in spirit approach, but one that gives us the best of both worlds, is to introduce a unary message curried used as a prefix to value: to indicate that currying is allowed. The example from the previous post then becomes

| add inc |
add := [:a :b | a + b].
inc := add curried value: 1.
(inc value: 2) + (inc value: 3)

This both preserves the familiar evaluation protocol and declares that currying is possible. To implement this, unlike in the Part 1 version, we leave valueWithArguments: unchanged from its standard implementation. Instead, we introduce a new class called Currier with an instance variable block and an initialization method block:, and add the following method to the block class (either BlockContext or BlockClosure, depending on the Smalltalk dialect):

curried
    ^Currier new block: self

This injects a Currier into the message stream as add curried value: 1 is evaluated, so that the value: message is received by the Currier. To process it, the Currier needs to invoke the original block if there are enough arguments to do so immediately, or create a CurriedBlock otherwise:

value: anObject
    block numArgs = 1
        ifTrue: [^block value: anObject].
    block numArgs > 1
        ifTrue: [^CurriedBlock new block: block arguments: {anObject}].
    self error: 'Incorrect number of arguments'

The above assumes that an expression such as {anObject} creates an Array with anObject, as in Squeak or in VisualWorks with my BraceConstructor extension.

Other value:... protocol messages are implemented similarly. Also, the CurriedBlock class should implement the same curried method as “real” blocks do–curried blocks are no different from regular ones, so they can be “re-curried”. (In Part 1 we would get such re-currying for free).

5 Responses to “Currying in Smalltalk, part 2”

  1. Travis Griggs says:

    So while this is really cool… what’s it good for Vassili? I’m asking a leading question here. :)

    I think currying remains a term reserved to “computer theory geeks.” Doing 3 + 4 in a way that looks like (2 + 1) + (3 + 1) -> 7 is lost on the unwashed masses. It’s like telling people that Seaside is cool because it’s based on continuations, and leaving it at that. So how ’bout a “chapter 3″ showing a case where the how to use this for normal programming is illustrated by example?

  2. Vassili Bykov says:

    Yep, you are onto something here, and there will be chapters 3 and 4 at least. :) I’m now in a middle of a move though, so I can’t promise a chapter a day…

  3. Björn says:

    What comes to my mind is
    If you want to define derivation by means of blocks you can do it as follows:
    df := [:f :x :h | (f value: x + h) - (f value: x) / h].
    func := [:x | x ** 3].
    df value: func value: 4711 value: 1000000 reciprocal

    Or you can use some kind of more explicit cyrrying (if one should call it currying…)
    curry := [:f | [:x :h | (f value: x + h) - (f value: x) / h].].
    cyrryDf := curryvalue: [:x | x ** 3] .
    cyrryDf value: 4 value: 100000000000 reciprocal

    In this way the add example in the text could be written
    | add inc |
    add := [:x | [:a :b | (a + x) + (b + x)]].
    inc := add value: 1.
    inc value: 2 value: 3

    Of course one has to bind both a and b simultaneously.
    Or rewrite the thing as
    | add inc |
    add := [:x | [:a | [:b | (a + x) + (b + x)]]].
    inc := add value: 1.
    inc := inc value: 2.
    inc value: 3

    Any comments on these more explicit alternatives?

  4. Vassili Bykov says:

    The alternatives show very well what currying (or in fact, multi-argument functions and their applications in general) is all about. If you imagine a system where blocks can only have one argument, you could implement multi-argument blocks by nesting, just like in your last example

    [:a1 :a2 ... aN | ...] => [:a1 [:a2 [... [:aN | ... ]]]]

    and rewriting every block invocation as nested ‘value:’ message sends

    aBlock value: a value: b value: c => ((aBlock value: a) value: b ) value: c

    The messages gradually “unwrap” the block. If there are enough of them, you end up running the inner block as in a “regular” block invocation. If not enough, you are left with a closure for the remaining layers, with the arguments provided so far captured in the closure’s environment.

Leave a Reply

For spam filtering purposes, please copy the number 6918 to the field below: