This post works towards an abstraction for currying in Ruby. It begins with an illustration of currying with pseudocode, then reviews the way Ruby does currying with procs. By thinking through the properties curried functions, it results in a
Curryable mixin that can be extended on a singleton class.
In mathematics and computer science, currying is the technique of transforming a function that takes multiple arguments (or a tuple of arguments) in such a way that it can be called as a chain of functions, each with a single argument (partial application)…
Take this function in pseudocode that adds three numbers:
addThreeNumbers(1, 1, 1) and it would return 3. But how is the function applied to those arguments?
addThreeNumbers as a Ruby method would use the arguments at any time they were referenced within it. It would raise an error if it were called with less than 3 arguments, given no arguments were optional.
add_three_numbers would act a lot differently if it were a curried function.
addThreeNumbers in our pseudocode. This means we’re going to transform it to accept one argument. The application of the function to that argument (the first) will return another function that accepts one argument and holds a reference to the argument from the previous application. Applying that function to an argument (the second) returns yet another function that accepts one argument and holds references to the arguments from the previous function applications. Applying the function to yet another argument (the third and final) returns the sum of all three arguments.
If open-closed parentheses were left-associative function applicators in our pseudocode, then
curry(addThreeNumbers)(1)(1)(1) would return 3.2 Furthermore, we could apply the function to less than 3 arguments and it would return a partially applied function. That partially applied function could be reused by applying it to more arguments later on.
an aside: Currying with lambdas in Ruby > 1.8
Ruby already allows for currying with lambdas. Calling
#curry on a lambda proc returns a curried version of the proc.
You probably noticed above that Ruby allows for multiple arguments to be called on curried lambdas. In our mixin abstraction for currying, we’ll stick to applying functions to one argument at a time.
A mixin abstraction for currying
What would an abstraction for currying look like in Ruby? Given a way to define a function, how might a
Curryable mixin for a singleton be implemented? With it, we could do this:
Thinking about the properties of curried functions will help us toward an abstraction. Here are a few:
- The length of the curried function’s function chain is equal to the function’s arity3.
- A function returns a partially applied function if it is applied to any number of arguments less than its arity. A partially-applied function has references to previous arguments.
- The function chain terminates when it is applied to the last argument and returns the result of the defined function.
A few things are clear. First, we need to allow for defining the function. Then we need a mechanism to generate a function chain that has the length of the function’s arity. Finally, we need to decide on a function applicator which will act as a mechanism to apply the function chain to each argument.
Allowing for the definition of a function is straightforward: just save a reference to the defined function on an instance variable.
Generating a function chain is less straightforward. Let’s take a look at a function chain for
add_three_numbers that uses lambdas.
Given the properties mentioned above, we can abstract away those repetitive lambdas with a little recursion.4
Finally, we need a function applicator. We’ll be using lambda procs in our function chain. Procs are invoked with
#call, and objects that implement that method get its alias, the epsilon method, for free.5 Starting out with the same method will give us a consistent applicator along the function chain.
Here’s the full version:
Thanks to John Norman for reading a draft of this post.
1 I actually rewrote a definition several times, attempting to pack in all of currying's intricacies. The first sentence of the Wikipedia entry explained it better than I ever could.
Calling a Haskell implementation with the same arguments looks like
addThreeNumbers 1 1 1. All of Haskell's functions are curried and spaces are left-associative function applicators. In fact,
addThreeNumbers 1 1 1 is syntactic sugar for
((addThreeNumbers 1) 1) 1. The latter version makes clearer how the function is applied to each argument at its particular point in the chain.
3 The arity of a function is the number of arguments it accepts.
The epsilon method is the method with zero characters. So for an object that implements
#call we would get the same result by doing
object.call(arg1, arg2) or