DEV Community

Cover image for Mastering Currying in JavaScript 🌟

Mastering Currying in JavaScript 🌟

Jagroop Singh on November 21, 2024

JavaScript is filled with cool features, and one of the most mind-blowing concepts is currying. Don’t worry if it sounds fancyβ€”by the end of this b...
Collapse
 
chasm profile image
Charles F. Munat

Your trick question goes beyond simple currying to add recursion.

add(1) returns a function that takes a single argument, call this addOne. LIke this:

const addOne = add(1) = (b) => b ? add(1 + b) : 1;
Enter fullscreen mode Exit fullscreen mode

This returns a closure where a is assigned 1.

add(1)(2) is equivalent to addOne(2) where 2 is the b argument. As b exists and is not 0, this returns the output of add(1 + 2) read add(3). This makes 3 the a in a new closure, so the function it returns could be called addThree.

const addThree = addOne(2) = add(1 + 2) = add(3) = (b) => b ? add(3 + b) : 3
Enter fullscreen mode Exit fullscreen mode

add(1)(2)(3) then is equivalent to addThree(3) which has a === 3 from the closure returned earlier and b == 3 from the argument. So again we recurse, returning add(3 + 3) or addSix.

const addSix = addThree(3) = add(3 + 3) = add(6) = (b) => b ? add(6 + b) : 6
Enter fullscreen mode Exit fullscreen mode

So add(1)(2)(3)(4) is addSix(4) where b === 4 hence it returns add(6 + 4) which we can call addTen.

const addTen = addSix(4) = add(6 + 4) = add(10) =
  (b) => b ? add(10 + b) : 10
Enter fullscreen mode Exit fullscreen mode

Finally add(1)(2)(3)(4)() calls addTen() with no arguments, which halts the recursion and just returns the a value (the sum) from the closure, which is 10.

addTen() // returns 10 as `b` is undefined hence
         // `b ? (add(10 + b) : 10` returns `10`
Enter fullscreen mode Exit fullscreen mode

We can see this more clearly like this:

const addOne = add(1)
const addThree = addOne(2)
const addSix = addThree(3)
const addTen = addSix(4)

addTen() // returns 10
Enter fullscreen mode Exit fullscreen mode

Easy peasy – if you understand currying, closures, and recursion.

Collapse
 
chasm profile image
Charles F. Munat

P.S. Your article doesn't really explore why currying is so useful (which is why ALL functions in Haskell are curried). Why would I need an addThree function?

The most obvious answer is the use of pipe or compose. Pipe is easier, so here's an example assuming curried arithmetic functions:

const doTheMath = pipe(add(3), divideBy(2), multiplyBy(10), subtract(25))

doTheMath(21)

// 21 + 3 => 24
// 24 / 2 => 12
// 12 * 10 => 120
// 120 - 25 => 95

returns 95
Enter fullscreen mode Exit fullscreen mode

Note, all functions passed to pipe (except the first one) must be unary, i.e., take only one parameter.

Collapse
 
jagroop2001 profile image
Jagroop Singh

Thanks for sharing your views.

Collapse
 
jagroop2001 profile image
Jagroop Singh

Wow, Nice explanation @chasm

Collapse
 
davepile profile image
davepile

This is well writen and the examples are very easy to follow and illustrate your points well. I have read a few articles about currying from time to time, but it always feels like a bit of mental gymnastics is required to use it. I can follow the examples, but its not something I can look at and instantly say "thats whats going on". Maybe it would become more intuitive if I used it more. Anyway, good write up.

Collapse
 
jagroop2001 profile image
Jagroop Singh

Thanks @davepile

Collapse
 
john12 profile image
john

`

`
const add = (a) => (b) => b ? add(a + b) : a;

console.log(add(1)(2)(3)(4)()); // ❓
`
`
It's answer would be : 10 ( if I uderstand the concept clearly)

Collapse
 
jagroop2001 profile image
Jagroop Singh

Yes it's correct !!
What's your approach to solve this.

Collapse
 
aniruddhaadak profile image
ANIRUDDHA ADAK

Absolutely incredible! 😻.

Collapse
 
jagroop2001 profile image
Jagroop Singh

Thanks @aniruddhadak

Collapse
 
aniruddhaadak profile image
ANIRUDDHA ADAK

It's fantastic.

Collapse
 
amos_murmu profile image
Amos Murmu

How does utility function works?? It's confusing
What does this code do
fn(...args)
: curry(fn.bind(null, ...args));

Collapse
 
chasm profile image
Charles F. Munat • Edited

This is actually a pretty smart bit of code. It allows you to curry any JavaScript function (remember that all functions must take at least one parameter, else they are effectively constants).

The function itself is curried, so you can pass in your function (sum) and get back a function that takes one or more arguments, which we collect into an array called args: (...args).

We can check how many arguments sum expects with length, hence fn.length here returns 3 because sum(a, b, c).

curriedSum(1, 2, 3) // returns 6
Enter fullscreen mode Exit fullscreen mode

So when the curried sum is called, we check if the number of arguments passed in (args) is equal to the number of parameters expected (fn.length). If all the arguments are there, then we just call the sum function with them, spreading the args back out: fn(...args).

But if too few arguments have been passed, e.g., sum(1, 2), then we bind those arguments to their parameters (here, a = 1 and b = 2) and return a closure that expects the third argument (c). When it gets that argument, it will meet the condition fn.length = args.length, so it will simply call the function with the remaining argument(s).

curriedSum(1, 2) // returns (c) => sum(1, 2, c)
Enter fullscreen mode Exit fullscreen mode

Hope that helps.

Collapse
 
soleng profile image
na.perruchot

I call that a fonctional paradigme.