Friday, January 11, 2008

Curry vs. Closure Wrapping in Groovy

I don't find the concept of currying a function that difficult. Of course, when something sounds easy to me it usually means I don't really understand it. The Wikipedia entry makes it sound complex, with all that mathematical notation, but the Groovy examples weren't all that hard to grasp. The question that I've been thinking about is, "Why would you use the curry() method to bind an argument instead of just binding it within another closure?" I'll try to answer this question shortly... but first let's review how curry() and wrapping things in closures are similar.

As a starting point, let's create a function that concatenates two parameters together, using a delegate as the delimiter. (Not to spoil the surprise, but this first example is going to evaluate to "Hello World").

def concat = { x, y ->
x + delegate.call() + y
}

concat.delegate = { " " }

assert concat("Hello", "World") == "Hello World"
So this simple function called concat can be used to combine strings. The delegate is a space, but could just as easily be a comma, a tab, or something else. Maybe this is useful if you need to build up long, delimited records in a file but don't know if they are comma or tab delimited until after the record is created. Maybe not. It's just an example.

Now let's see curry and closure wrapping in action. Say you wanted the same function except you always wanted to use "Hello" as the first argument. There are two ways to use the concat closure and permanently bind "Hello" as the first parameter. You can wrap concat in another closure, like this:
def hello = { x -> concat("Hello", x) }
assert hello("World") == "Hello World"
Or you can use the curry function to do the same:
def hello = concat.curry("Hello")
assert hello("World") == "Hello World"
In the first example, the hello object is an instance of Closure. In the second example, the hello object is an instance of CurriedClosure. However, you probably don't care because they share the same interface.

The limitation of the curry method is that arguments are bound from left to right in the argument list. So curry can be used to bind "Hello" onto concat because we are binding the left most argument ("Hello"). But we couldn't have used curry if we had wanted to bind "World", because there is no way to bind a right argument; arguments are always bound from left to right in the parameter list. The opinion on the Groovy mailing list seems to be that this is the standard implementation of curry(), and someone wanted a curryN() method could easily add it themselves. In languages with named arguments it would be possible to bind an arbitrary argument, and it also seems you can do this in Python using the Xoltar toolkit.

So if curry() has a left to right binding limitation, and a simple alternative exists, then why use curry at all?

The answer lays in how delegates are treated.

The delegate of the CurriedClosure is the same as the delegate of the original Closure. So, in the concat example, you can still change the delimiter after the closure has been curried:
def hello = concat.curry("Hello")
hello.delegate = { "," }
assert hello("World") == "Hello,World"
If you had wrapped the concat closure in a new closure, then setting the delegate no longer has any effect on the original one:
def hello = { x -> concat("Hello", x) }
hello.delegate = { "," }
assert hello("World") == "Hello World"
Pretty cool. Possibly useful. Maybe complex?

So what next... right to left argument binding would be pretty easy to implement based on how simple the original is. Completing the JSR for Named Parameters would let you do the same. This problem doesn't really bother me though. For me, the coolest change would be to allow in-place binding of arguments on the function, so that you could hold a reference to a Closure object and have some other collaborator bind an argument on it without you having to change the reference. Currently, curry() returns a new CurriedClosure reference, so anyone with a handle to the old one won't see the bound argument. I have zero background in functional programming, so this is probably a god-awful idea. But it would be kinda cool.

More info on this stuff can be found here, here, and here, and source to tinker is available, too.

2 comments:

Bernd Schiffer said...

Great Post! You widend my horizont about currying - it wasn't clear to me what to use as an alternative when I wanted to set the delegate on a curried closure. Now everything's clear to me - thanks to you!

Saager Mhatre said...

+1 for observation!
Cool point about delegating delegate assignment! :P
I completely missed that when I was trying to compare the two. ;)