Saw this question in an interview some time ago:
Create a method curry that when passed in a function, will return a curried version of the function:
const join = (a,b,c) => {
return `${a}_${b}_${c}`
}
const curriedJoin = curry(join);
console.log(curriedJoin(1)(2,3)); //1_2_3
console.log(curriedJoin(1,2)(3)); //1_2_3
console.log(curriedJoin(1)(2)()()(3)); //1_2_3
console.log(curriedJoin(1,2,3)); //1_2_3
What is currying?
Wikipedia says:
In mathematics and computer science, currying is the technique of converting a function that takes multiple arguments into a sequence of functions that each takes a single argument.
A function that takes multiple arguments is transformed into a series of functions that take single arguments.
Initial approach
Based on the definition this is how the function is supposed to be structured:
const curry = (fn) => {
const curriedFunction = (...args) => {
};
return curriedFunction;
};
The function takes an argument function fn
and returns a new function.
The returned function expects arguments, and for ease of handling ...args
(Rest Operator) is used. It is a way to handle indefinite number of arguments. Now args
is an array having all the function arguments.
Returning a function
Every time the curried function is called, a function has to be returned. It is also necessary to keep track of the arguments passed at every step of the sequence. When we want to execute the function, we can call fn
with ...args
.
const curry = (fn) => {
const curried = (...args) => {
let arr = [];
arr = [...arr,...args];
const innerFunc = (...innerArgs) => {
arr =[...arr,...innerArgs];
if(arr.length === fn.length){
return fn(...arr);
}
else{
return innerFunc;
}
};
return innerFunc;
}
return curried;
};
fn.length
returns the number of parameters expected by fn
. A javascript function can take as many arguments as passed, but this value will always return the number of formal parameters.
It is useful in the base condition. As soon as the length of the array is equal to the number of parameters expected, the callback function can be called.
Edge conditions
The above solutions solve most of the cases except this:
console.log(curriedJoin(1,2,3));
To handle this, the function should have another check in case all the arguments are passed in the first go itself.
const curry = (fn) => {
const curried = (...args) => {
let arr = [];
if(args.length === fn.length){
return fn(...args);
}
else{
arr = [...arr,...args];
const innerFunc = (...innerArgs) => {
arr =[...arr,...innerArgs];
if(arr.length === fn.length){
return fn(...arr);
}
else{
return innerFunc;
}
};
return innerFunc;
}
}
return curried;
}
Summary
The question tests the solver's understanding of closures. It also relies on the fact that the solver is aware of the length property of the Function prototype, but that is not the main point. Since there is a lot of nesting it is a good way to check how clearly the solver thinks in complicated scenarios.
Let me know in comments how you would solve it?
Top comments (6)
My answer would be:
No I won't write it, currying is an inefficient antipattern in this example and then I'd walk out π
Thanks for a well written post though, the concept might sound confusing.
God, you must be fun at interviews :P
Just kidding.
Honestly, I have also not used something like this in real world.
Under the right circumstances the strategy of calling out the drawbacks, running the interview (but doing the technical task well) can lead to a stronger position. And no, no I'm no fun π
True. That makes sense.
Follow up : How would you change your code if you have to handle this case too:
Hai I have reduced this custom curry function as below: