Here is a minimal curry function
const curry = (fn, ...args) =>
args.length >= fn.length ? fn(...args) : curry.bind(null, fn, ...args)
High Level Explanation:
We want to construct a function, similar in spirit to Thrush ( f => a => f(a)
) but with variadic inputs. We want to partially apply input to this function, passing in the curried function f
for the first parameter and the rest of the parameters needed until the appropriate arity for our function, given by f.length
is met or exceeded.
Details:
Suppose we have some add function,
const add = (a,b,c) => a+b+c
and we curry it
const curriedAdd = curry( add )
Here is what happens:
- Our curry function receives a function, with no additional arguments (note: we could have passed parameters at the time of the currying, ie
curry( add, 10 )
)
- The predicate
args.length >= fn.length
is false
because we provided no args
and the function has a length of 3
- We bind to a new copy of curry our original function and all of our arguments (no arguments)
Cool so we basically just get the same function back only now its bound to curry
Next we call it thus
const inc = curriedAdd(0,1)
Now the following happens
We invoke the curried function. curriedAdd
is add
bound to it as the first parameter (after this
is set to null
). It looks like this
const inc = curry.bind(null,add)(0,1)
Here when we invoke curry, add
is again the first parameter of the function. args
is now a list of two [0,1]
.
- The predicate
args.length >= fn.length
is therefore false because add.length
is 3 and args.length
is two.
- We now bind to yet another fresh copy of
curry
and bind that to add
with two parameters [0,1]
spread into bind.
inc
is not curry.bind(null, add, 0, 1)
Cool, so now we call this
const six = inc(5)
But inc
is just curry.bind(null,add,0,1)
Thus we called curry
as before. This time args.length >= fn.length
is true
and invoke add
with all three parameters
An important part of this currying function is that the predicate be args.length >= fn.length
and not args.length === fn.length
because otherwise this would fail
const six = inc(5,undefined)
This may seem not important, however, in Javascript you might often do something like this
const concatWith = curry( (fn,a,b) => a.concat(fn(b)) )
const collectObjectValues = concatWith(Object.values)
[ {a: 1, b: 2}, {c: 3} ].reduce( collectObjectValues, [] )
// [1,2,3]
The reduce
function passes in a few parameters... a number greater than our expected two (see footnotes). If our curry predicate didn't account for the greater than scenario, this code would break.
Hope this was informative and educational. Enjoy!
Footnotes:
[1] - four to be exact, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce