Currying is a technique of converting a function that takes n-number parameters to an n-chain of functions that takes one parameter at a time until all n-number parameters are collected and the function is evaluated. Consider the functions
const sum2 = (a,b) => a + b
const sum3 = (a,b,c) => a + b + c
We could define them in curried form by writing them like this:
const sum2 = a => b => a + b
const sum3 = a => b => c => a + b + c
A practical reason to favor curried form occurs in certain situations where you want automatic partial applications. Consider the two increment functions. The first
incrA where it is built off of curried form
sum2 function and the second where
incrB it is built off of a traditional (also called tupled form)
// if sum2 is in curried form
const incrA = sum2(1)// if sum2 is in tupled form
const incrB = (b) => sum2(1, b)
The latter requires some boilerplate while the former reads a little bit clearer. Writing a function that takes a 2-tuple or 3-tuple input in a curried form is relatively trivial. However as the number or parameters increase writing in curried form can be a bit gnarly. We can solve this by creating a magic function that does this for us.
Consider a very specific function
curry2 that takes a 2-tuple and converts it to its curried form:
const curry2 = f => a => b => f(a, b)
curry2 takes as input a function
f (e.g. sum2) and then each individual argument
b and when it has both of them invokes the captured function
f with those parameters.
const sum2Tupled = (a,b) => a + b
const sum2Curried = curry2(sum2Tupled)
While we can work on a number of utility functions curry2, curry3, curry4 etc. However there is a slightly better way — we can leverage
- variadic input
- spread operator
- and papply
Lets take this backwards.
papply is a utility function that takes a function and an input and partially applies that input to that function. The effect is similar to that of a function defined in a curried form, but lacking somewhat in syntactic sugar.
// consider sum2 defined in tupled form
const incr = papply(sum2, 1)
This utility (papply) is fairly easy to write:
const papply = (f, x) => f.bind(null, x)
The next step is defining
reduce. As we know (article on reduce) that
reduce has the intent of aggregating a list into a single value through a self-provided reducing function:
const head = xs => xs
const tail = xs => xs.slice(1)
const reduce = (f, agg, list) =>
(list.length == 0) ?
reduce(f, f(agg, head(list)), tail(list)) ;
To define the magical
curry function we need to combine the papply with reduce and the magical spread operator:
const curry = f =>
(f.length == 0) ?
(...xs) => curry(reduce(papply, f, xs)) ;
curry takes a function
f. If that functions has had all of its arguments bound, it is invoked and the result are returned. If that function (
f) still has free arguments
curry then returns an anonymous function that takes any number of inputs, captured as an array via spread operator
…xs. Those inputs are then partially applied onto
reduce. We then
curry that result which starts the entire process over (i.e. check to see if all of the arguments are bound).
To remind ourselves of how reduce works to sum lists:
const add2 = (a,b) => a + b// using a list processing library
const sumList = list => reduce(add2, 0, list)
reduce takes something that combines two objects — in the case of
sumList this is
add2. The combiner takes these objects in a particular order. The first object is the running aggregate. The second object is the next thing to be applied to that aggregate.
Reduce also takes an initial value — in the case of
sumList this is
0. Lastly it takes the list
Applying this thought process to
reduce(papply, f, xs)
The inital value is the captured function
f; In this case it has free parameters. The combining function here is
papply which creates a new function by taking a function (the running aggregate) and any one parameter and partially applying it.
reduce repeats this process for every item in the list
We can then apply this version of curry to any tupled/traditionally defined function. But please leverage a version from either Ramda or Sanctuary. My implementation doesn’t handle:
- the attempt to curry a function that doesn’t take input
- the application of more parameters than the captured function intends to take