'Curry' is a name for a utility function commonly employed in a functional programming context. Typically, the curry function takes in a function with arity N ('arity' simply means 'the number of arguments a function takes') and returns a function with an arity of 1.
When called, the returned function applies its argument to the original function, and then returns a new function with arity of one. When called, this returned function applies its argument, in turn, returning another function with an arity of one, and so on-- until all of the arguments have been passed in to the original function, at which point it is actually called and the actual result is returned.
We can manually curry funcitons without a currying utility function. For example:
cconst addThreeNumbers = (n1, n2, n3) => {
return n1 + n2 + n3
}
addThreeNumbers(1, 2, 3); // returns 6
const manuallyCurriedAddThreeNumbers = n1 => {
return n2 => {
return n3 => {
return n1 + n2 + n3
}
}
}
manuallyCurryAddThreeNumbers(1)(2)(3) // returns 6Here's what it might look like to use a curry utility function--something we might import from a functional programming library like ramda.js or lodash.js--to automatically curry addThreeNumbers:
const functionallyCurriedAddThreeNumbers = curry(addThreeNumbers)
functionallyCurriedAddThreeNumbers(1)(2)(3) // returns 6In other words, curry transforms a function taking many arguments all at once into a function which takes those arguments one at a time.
Today we'll be writing a modified version of curry which has the behavior demonstrated above, but whose returned function has an arity of N. That is, we should be able to pass in arguments one at a time, two at a time, ... N at a time. This is the variadicCurry function, where 'variadic' just means 'with variable arity'. It can be used like this:
const curriedAddThreeNumbers = variadicCurry(addThreeNumbers)
curriedAddThreeNumbers(1)(2)(3) // returns 6
curriedAddThreeNumbers(1, 2)(3) // returns 6
curriedAddThreeNumbers(1)(2, 3) // returns 6
curriedAddThreeNumbers(1, 2, 3) // returns 6There's one more constraint on the solution we need to mention. Functions returned by variadicCurry needs to be re-usable. For example:
const print2 = (s1, s2) => console.log(s1 + ' ' + s2);
const curriedPrint2 = variadicCurry(print2);
const partialA = currentPrint2('React is');
const partialB = currentPrint2('Express is');
partialA('a front-end framework'); // React is a front-end frameowrk
partialB('a back-end framework'); // Express is a back-end frameworkHint 1: make use of .bind(); you don't need to worry about functions passed to variadicCurry making use of this. Hint 2: remember that you can call .length on a function to get its arity.
One way you might try to get started is as follows:
function variadicCurry(fn) {
const memory = [];
const originalLength = fn.length;
return function innerFunc(...args) {
// logic for dealing with memory/args goes here
};
}Notice that this won't pass the re-usability test. If we called our print2 function as defined above, the express and react calls would write to the same memory array.
There are ways around this involving IIFEs, but working out the logic inside innerFunc would prove challenging. While not impossible, a much simpler solution is available if we employ recursion.
How can we tell that recursion will be useful, here? We know that the function variadicCurry returns needs to have some branching logic: when we have enough arguments to evaluate the original function, we need to do so; when we don't have enough arguments, yet, we need to return another function.
Moreover, the function we return in the latter case--the case in which we do not have all of the arguments yet--has to have all of the same properties and behavior as the function which returns it. That is, the function returned when we don't have all of the arguments yet needs to itself be variadically curried.
"Having all of the arguments" and "not having all of the arguments yet" are thus quite good candidates for a base case and recursive case.
const variadicCurry = (fn) => {
return () => {
if (//base case condition) {
// base case action
} else {
// recursive case action... returns a function
}
}
}How do we tell which case we're in? Well, we can take in any number of arguments with the rest operator: ...args as the parameters to a function. And we can measure the arity of our original un-curried function like this: fn.length.
So our base case is when args.length is greater than the length of our original function, in which case we can call it with the spread operator and return it: fn(...args).
const variadicCurry = fn => {
return (...args) => {
if (args.length >= fn.length) {
return fn(...args);
} else {
// recursive case action... returns a function
}
};
};What to put in the recursive case? We know we want to partially apply the arguments we have to our function and then return a curried version of it. We can use .bind to do the partial application.
Before we finish the solution, notice that partial application with bind decreases the length of the return, telling us how many arguments are left to apply:
const takesTwo = (x, y) => x + y;
takesTwo.length; // 2
const takesOne = takesTwo.bind(null, 42);
takesOne.length; // 1This means that when we recursively call variadicCurry on the partially applied function, our base case condition will work properly within the returned function: the curried, partially applied function we return will have a length property which shows how many arguments are left to apply. Here's the last step:
const variadicCurry = function(fn) {
return (...args) => {
// this is returned
if (args.length >= fn.length) {
return fn(...args);
} else {
const partiallyAppliedFn = fn.bind(null, ...args);
return variadicCurry(partiallyAppliedFn);
}
};
};And here it is as a less readable, gee-whiz one-liner:
const variadicCurry = fn => (...args) =>
args.length >= fn.length ? fn(...args) : variadicCurry(fn.bind(null, ...args));