A Little Introduction
Currying happens when a function does not receive all of its parameters at once. It instead takes the first parameter and returns a different function. The returned function should be invoked with the second parameter, which should return another function.This continues until all the arguments have been presented. The function at the end of the chain will then be the one that returns the desired value.
// This is a normal function
sum(1,2,3)
// while this is a curried function
sum(1)(2)(3)
It is important to note that currying only alters the construct of a function and not it's functionality.
Question-1: sum(1)(2)
This is fairly straightforward. The syntax might be a bit weird to those who see it for the first time. Basically, we keep returning a function and pass it one argument. This continues after we have finished passing all the arguments. At the last stage, we have access to all the previously passed arguments due to the power of closures. It is at this stage, that we execute our logic.
function sum(a){
return function(b){
return a + b;
}
}
// dry-run
// add(1)(2)
// step - 1: add(1) executes and returns a function
// (function(b){
// return 1 + b;
// })(2)
// step - 2: returned function executes with 2 passed as parameter.
// (function(2){
// return 1 + 2 //> it gets value of 1 from closure!
// })
// outputs 3
console.log(sum(1)(2)) //logs 6
Question-2: sum(1)(2)(3)(4)...(n)()
This question is a direct extension of the first one. Catch is, number of arguments can be infinite! This problem is commonly known as infinite currying and is a popular interview question.
Always solve for a smaller subproblem.
// with 2 arguments
function sum2(a){
return function(b){
return a + b;
}
}
// with 3 arguments
function sum3(a){
return function(b){
return function(c){
return a + b + c;
}
}
}
// notice the pattern here ? At every level,
// If there is another argument left, we return a new function
// else we execute the logic. Recursive thinking is required for // these pattern of problems.
//with n-arguments
function sum(a) {
return function(b){
if(b){
return sum(a+b);
}
return a;
}
}
// dry-run
// example -> sumN(1)(2)(3)(4)
// step-1
// sumN(1) executes
// step-2
// (function(b){
// if(b){
// return sumN(1+b);
// }
// return a;
// })(2)(3)(4)
// step-3
// (function(2){
// if(2){
// return sumN(1+2); // again call to sum
// }
// //> won't execute
// return a;
// })(3)(4)
// // Notice the recursive pattern ?
// step-4
// (function sumN(3) {
// return function(b){
// if(b){
// return sumN(3+b);
// }
// return a;
// }
// })(3)(4)
// step-5
// (function sumN(3) {
// return function(3){
// if(b){
// return sumN(3+3);
// }
// return a;
// }
// })(4)
// step-6
// (function sumN(6) {
// return function(b){
// if(b){
// return sumN(6+b);
// }
// return a;
// }
// })(4)
// step-7
// (function(4){
// if(4){
// return sumN(6+4);
// }
// return a;
// })
// step-8
// sumN(10)
// step-9
// (function(undefined){
// if(undefined){
// //> won't execute
// return sum(a+b);
// }
// return 10;
// })()
// step-10
// logs 10
I'd highly recommend writing the code in any IDE and do a dry run yourself
Question-3 sum(1,2,...,n)(3,4,...,n)...(n)()
We are stepping up the game here. In this pattern, number of arguments in a single call can be infinite as well! We will use knowledge gained from the last problem to solve this.
hint: this problem can directly be reduced to the last problem
// we just reduce the arguments into a single argument for
// a single call! Now it's just like the previous problem!
function sum(...args) {
let a = args.reduce((a, b) => a + b, 0)
return function(...args){
let b = args.reduce((a, b) => a + b, 0)
if(b){
return sum(a+b)
}
return a
}
}
Question-4 implement function currify
Currify will be a function that takes in another function as argument and returns a currified version of the same function! Note that it does not modify the functionality of the passed function by any means. It only transforms the function.
Now, this one is a little tricky.
The general idea to solve the problem is like this:
- Currify will take a function as argument.
- Currify will return another function.
- The use-case of that function will be to somehow store all the arguments that are being passed, until there are no more arguments to pass.
- Once all the arguments are exhausted, we can simply call the passed function with all the accumulated arguments and our job will be done.
below is the above idea expressed in code.
function currify(fn) {
return function saveCurryArgs() {
const args = Array.prototype.slice.call(arguments); // a
if (args.length >= fn.length) // b
return fn.apply(null,args); //c
else
return saveCurryArgs.bind(null, ...args); //d
}
}
const multiply = function(a,b,c){
return a + b + c
}
const curryMultiply = currify(multiply);
// logs 6
console.log(curryMultiply(1)(2)(3));
Let's understand exactly what it's doing and how it's doing it.
I've commented main logic points in the code with alphabets.
a) We construct our list of arguments from the array-like object arguments.
b) args.length represents arguments received so far. fn.length denotes the total number of arguments that are to be processed.
c) This logic executes after we have exhausted all our arguments. We simply use the apply method on fn.
d) Else, we save the current arguments with all the previous arguments by calling bind over the saveCurryArgs function.
With this, our logic for currify is complete!
Conclusion
If you've managed to come this far, congratulations! Currying is an extremely important topic from an interview point of view as well as real world use-cases. I'd again recommend coding out the examples over codepen or your local IDE. Hope you enjoyed the article! Thanks for the read!
Top comments (4)
Lovely explained!
For this sum I always come up with a quick solution to it:
TS version:
I feel it a bit silly to use currying to sum or multiply numbers. On the other hand, currying has a good value on other use-cases such the following example:
This way you can check if a number is divisible by another one like that:
Or maybe refactor it a little bit to return a boolean:
This way:
Last but not least, if we swap the args this:
We can extend the functionality pretty easily thanks to currying:
Happy coding!
Damn! Learnt a lot! Thanks for your comment!
Beautiful