DEV Community

Cover image for Function Currying: Javascript Questions
Soumava Banerjee
Soumava Banerjee

Posted on

Function Currying: Javascript Questions

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)
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

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 (2)

Collapse
 
joelbonetr profile image
JoelBonetR 🥇 • Edited

Lovely explained!

For this sum I always come up with a quick solution to it:

/**
 * Sums all the numbers received as args
 * @param  {...number} args
 * @returns {number}
 */
const sum = (...args) => args.reduce((n1, n2) => n1 + n2);
Enter fullscreen mode Exit fullscreen mode

TS version:

const sum = (...args: Array<number>) => args.reduce((n1, n2) => n1 + n2);
Enter fullscreen mode Exit fullscreen mode


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:
const divisible = num => mod => num % mod;
Enter fullscreen mode Exit fullscreen mode

This way you can check if a number is divisible by another one like that:

divisible(10)(2); // 0
Enter fullscreen mode Exit fullscreen mode

Or maybe refactor it a little bit to return a boolean:

const divisible = num => mod => num % mod === 0;
Enter fullscreen mode Exit fullscreen mode

This way:

divisible(10)(2); // true
divisible(10)(3); // false
Enter fullscreen mode Exit fullscreen mode

Last but not least, if we swap the args this:

// OLD
const divisible = num => mod => num % mod === 0;
// NEW 
const divisible = mod => num => num % mod === 0;
Enter fullscreen mode Exit fullscreen mode

We can extend the functionality pretty easily thanks to currying:

const isDivisibleBy3 = divisible(3);

isDivisibleBy3(10) // false
isDivisibleBy3(12) // true
Enter fullscreen mode Exit fullscreen mode

Happy coding!

Collapse
 
soumavabanerjee profile image
Soumava Banerjee

Damn! Learnt a lot! Thanks for your comment!