DEV Community

Cover image for Introduction to Currying in JavaScript
Alex Devero
Alex Devero

Posted on • Originally published at blog.alexdevero.com

Introduction to Currying in JavaScript

Currying is one of the more advanced techniques of working with functions. What it does is it allows you to transform functions, and the way your work with them. This tutorial will help you understand what currying in JavaScript is, how it works and how to use it in your code.

What is currying

First of all, currying exists in multiple languages, not just JavaScript. There are probably multiple ways to explain what currying is. Some simple? Let's start with this. Currying is a process. It is a process of transforming functions with specific number of arguments into a sequence of nested functions.

Each of these functions in the sequence is being returned and each is passed only one of the arguments. Only the last function in the sequence takes all arguments spread through the sequence, does some operation, and returns a value, or values. This is the first transformation.

// Curried function example:
function curriedFn(a) {
  return function(b) {
    return function(c) {
      return a + b + c
    }
  }
}

// Normal function:
function fn(a, b, c) {
  return a + b + c
}
Enter fullscreen mode Exit fullscreen mode

The second transformation is how you call, or invoke, curried function. Usually, you would pass all required arguments inside the one set of parentheses. Not with curried functions. When you work with curried functions, you pass each argument into a separate set of parentheses.

// Calling curried function declared above:
curriedFn(11)(22)(33)
// Output:
// 66

// Calling normal function:
fn(11, 22, 33)
Enter fullscreen mode Exit fullscreen mode

How currying works

Currying can look as something unusual especially for beginners. Let's take a look at how currying in JavaScript works, and why it can even work.

The thing about values, arguments, and closures

How easy or difficult it is to understand currying in JavaScript might well depend on how familiar are you with the concept of closures. It is thanks to closures currying works. Here is how these two work together. As you can see on the example, Each function in the sequence works only with a single argument.

It would make sense that when each function is called, the value passed into it as an argument is lost as well. This is not the case. The value still exists in the scope of the function that was called. What's more important is that any function inside this scope can access this scoped value as well.

All these values exist, and are accessible, as long as the execution of the sequence is going. When it terminates with the last function and the value it returns, these existing values are gone as well. This is also why the last, innermost, function can operate with all previously seen arguments.

In case of this last function in the sequence, the innermost, all these values still exist. This is also why it can work with them.

function curriedFn(a) {
  // Argument "a" exists here
  return function(b) {
    // Argument "a" and "b" exist here
    return function(c) {
      // Argument "a", "b" and "c" exist here
      return a + b + c
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The thing about parentheses

So, the innermost function can return all previously seen values because they are kept alive thanks to closure. What about those additional parentheses? These parentheses serve two main purposes. First, they allow to pass specific argument to specific function.

This is determined by the order in which arguments are defined in the curried function. The second thing is more important and interesting. Each of these additional parentheses is actually a new function call. This means that when you see curried function with three pairs of parentheses, you are looking at three function calls.

Each of these calls invokes one of the functions in the sequence, while also providing required argument for that function.

// Create curried function:
function curriedFn(a) {
  return function(b) {
    return function(c) {
      return a + b + c
    }
  }
}

// Calling curried function:
curriedFn(11)(22)(33)

// can be visualized as:
outermostFn(11) // curriedFn(a) { ... }
middleFn(22) // function(b) { ... }
innermostFn(33) // function(c) { ... }
Enter fullscreen mode Exit fullscreen mode

This is also why currying allows you to call each function "manually". Each call returns a function. What you can do is to take each function call and sequentially assign its returned value to a variable. Each of these steps will result in variable assigned a function, except the last one.

The last variable will be assigned the value returned by the last function. This last value is what you get when you call curried function with all required argument, and pair of parentheses. The only difference are those extra lines and assigned variables.

// Create curried function:
function curriedFn(a) {
  return function(b) {
    return function(c) {
      return a + b + c
    }
  }
}

// This:
curriedFn(11)(22)(33)

// is the same as (except those extra lines):
const firstCall = curriedFn(11)
const secondCall = firstCall(22)
const lastCall = secondCall(33)

console.log(firstCall)
// Output:
// ƒ ()
// That is:
// function(b) {
//   return function(c) {
//     return a + b + c
//   }
// }

console.log(secondCall)
// Output:
// ƒ ()
// That is:
// function(c) {
//   return a + b + c
// }

console.log(lastCall)
// Output:
// 66
// That is:
// a + b + c
Enter fullscreen mode Exit fullscreen mode

Curried functions without arguments

Currying is usually used for functions that are defined with some parameters. However, this is not a rule. You can just as well create curried function that doesn't take any arguments. In this case, you still have to provide correct number of parentheses, just empty.

// Create curried function:
function curriedFn() {
  return function() {
    return function() {
      return function() {
        return function() {
          return '??'
        }
      }
    }
  }
}

// Call curriedFn():
curriedFn()()()()()
// Output:
// '??'
Enter fullscreen mode Exit fullscreen mode

Currying arrow functions

Just as you can curry regular functions, you can also curry arrow functions. This can help you reduce the amount of code you would otherwise have to use. The principles and way to use it is still the same. Only the syntax is different, due to the nature of arrow functions.

// Regular curried function:
function curriedFn(a) {
  return function(b) {
    return function(c) {
      return a + b + c
    }
  }
}

// Arrow function alternative:
const curriedFn = (a) => (b) => (c) => a + b + c

// Calling the curried function:
curriedFn(11)(33)(55)
// Output:
// 99
Enter fullscreen mode Exit fullscreen mode

Partial application functions

When we talk about currying in JavaScript, it is also useful to mention technique called partial application. The reason is that these two are very similar, so similar that it can be confusing. However, there is one key difference that will help you distinguish between them.

This difference is in the number of parameters. When you curry a function, each function in the sequence accepts only one parameter. This is not the case with partial application. In case of partial application, the rule is that the newly returned functions must accept fewer parameters.

This means that there might still be arguments spread across multiple pairs of parentheses. However, some of these pairs of parentheses will contain more than just one argument. When you see something like this, you are looking at partial application function, not curried function.

// Curried function example:
function myCurriedFn(x) {
  return function(y) {
    return function(z) {
      return function(w) {
        return x * y * z * w
      }
    }
  }
}

myCurriedFn(3)(6)(3)(9)
// Output:
// 486


// Partial application function example:
function myPartApplicationFn(x) {
  return function(y, z) {// Passing two arguments instead of one
    return function(w) {
      return x * y * z * w
    }
  }
}

myPartApplicationFn(3)(6, 3)(9)
// Output:
// 486
Enter fullscreen mode Exit fullscreen mode

Conclusion: Introduction to currying in JavaScript

The concept of currying can be confusing and difficult to grasp. Just the word itself can sound weird. The syntax also isn't much helpful. I hope that this tutorial was helpful in shading some light on this topic, helping you understand how, and why, currying in JavaScript and how to use it.

Discussion (17)

Collapse
ridays2001 profile image
Riday

Why would you need currying? I mean, wouldn't a normal function make your life much easier? I believe with currying (especially in a versatile language like JS), you're introducing unnecessary complications.

Collapse
vonheikemen profile image
Heiker

Need is a strong word. I guess it becomes necessary when you're heavily invested in function composition, higher order functions and point free style.

Say you have a process where you get name and lastname from a list of users that comes from a server. If I'm in a hurry I'll just do something like this.

fetch('/users').then(users => user.map(({name, lastname}) => ({name, lastname})));
Enter fullscreen mode Exit fullscreen mode

If you want to make it more readable you'll put every step in a function.

const public_info = ({name, lastname}) => ({name, lastname});
const user_list = (users) => users.map(public_info);

fetch('/users').then(user_list);
Enter fullscreen mode Exit fullscreen mode

In the code above we are already using some sort of partial application and we see an improvement. But if we take a step further and implement a bunch of utilities that are curried by default we could end up with something like this.

const public_info = pick(['name', 'lastname']);
const user_list = map(public_info);

fetch('/users').then(user_list);
Enter fullscreen mode Exit fullscreen mode

Or, again if you're in a hurry, put everything in one line.

fetch('/users').then(map(pick(['name', 'lastname'])));
Enter fullscreen mode Exit fullscreen mode

See how well these things work together. Really, any functional programming pattern works best when you combine them with others.

And this is the part where people say "I could just do X", and that's ok. Javascript is an imperative language, you don't need fancy patterns.

Collapse
kalitine profile image
Philippe Kalitine

There is no carrying in your example, am I wrong ?

Thread Thread
vonheikemen profile image
Heiker • Edited on

Let's just say is not explicit. I didn't want to put to much code into that comment.

In my example map and pick should be curried.

function pick(keys) {
  return function(data) {
    let result = {};

    for(let idx of keys) {
      if (idx in data) {
        result[idx] = data[idx];
      }
    }

    return result;
  }
}

function map(fn) {
  return function(data) {
    return data.map(fn);
  }
}
Enter fullscreen mode Exit fullscreen mode

Notice how those two functions are curried and their last argument is data? This is important because it what allows this type of composition.

const process_users = map(pick(['name', 'lastname']));
Enter fullscreen mode Exit fullscreen mode

And so we have created a new function (process_users) in a way that feels declarative.

If map and pick were not curried you'll have to do this.

const process_users = (users) => map(user => pick(['name', 'lastname'], user), users);
Enter fullscreen mode Exit fullscreen mode

Currying will bring you the most benefits when you're trying to do function composition. That is combining two or more functions to create a new one. If you're not interested in function composition then currying will just feels useless.


EDIT: So I just remembered I wrote something about this topic here on dev:
Partial application
Composition techniques

Thread Thread
kalitine profile image
Philippe Kalitine

Ok, now I see the currying explicitly, thank you for the explanations and I also read your "Functional programming for your everyday javascript: Partial application" to understand better the currying approach.

If we come back to @ridays2001 comment, I still don't see the point of currying. It feels like the main point is better code readability/complexity ? A different way of thinking at how we look at and approach the data transformation process which is somehow more interesting and fun ? Is that is about ?

Thread Thread
vonheikemen profile image
Heiker • Edited on

What I'm about to say is just my opinion. Not going to claim this is the absolute truth.

"The point" of currying is to enable better function composition.

Let me present a very popular function called pipe.

function pipe(...chain_of_functions) {
  return (init) => {
    let state = init;

    for(let func of chain_of_functions) {
      state = func(state);
    }

    return state;
  }
}
Enter fullscreen mode Exit fullscreen mode

This allows us to convert this.

last_fun(second_fun(first_fun('something')));
Enter fullscreen mode Exit fullscreen mode

Into this.

const final_fun = pipe(first_fun, second_fun, last_fun);

final_fun('something');
Enter fullscreen mode Exit fullscreen mode

This seems cool... until you realise each function just takes one argument. We got ourselves a real problem here. We want to create functions with pipe (we can't change its implementation) and we also want to use functions with multiple arguments.

One answer to this problem is currying. If literally all your functions take one argument you'll never have to face this issue while using pipe.

Let's say that second_fun needs two arguments. Fine. Curry it.

const second_fun = (one) => (two) => {
  // do stuff
}
Enter fullscreen mode Exit fullscreen mode

Now you're free to do this.

const final_fun = pipe(first_fun, second_fun('WOW'), last_fun);
Enter fullscreen mode Exit fullscreen mode

It'll work like a charm.

But is this better? More readable? I can't answer that for you. It's a matter of style. Do what makes you happy.

Collapse
kresnik profile image
Kyle Resnik

Maybe I'm wrong, but I got the impression that currying lets you break up and organize processes in a block statement. Might only be relevant in a particularly long-winded function.

Collapse
jonrandy profile image
Jon Randy • Edited on

Great intro. I'm considering writing a library using my recent project so that we can easily use curried versions of our functions using the following syntax:

function add(a, b) {
  return a+b
}

const add3 = add[curried](3)

console.log(add3(2))  // 5
Enter fullscreen mode Exit fullscreen mode

What do you think?

Collapse
benbonnet profile image
Ben

given the examples, the unadvised user will only see that fn(a, b, c) can be written another way fn(a)(b)(c)

quite sure there would be slightly more complex examples to show why currying can be cool

Collapse
tobisgd profile image
TobiSGD

What exactly is the advantage of this? All I can see is the introduction of a bunch of unnecessary context switches, but maybe I am missing something essential here.

Collapse
fischgeek profile image
fischgeek

It's common in functional languages to pass functions to eachother and not data.

Collapse
tobisgd profile image
TobiSGD

That's nice to know, but sadly doesn't answer the question. Why would I do that to my JS code? What are the advantages and do they outweigh the disadvantages of introducing all these context switches?

Thread Thread
fischgeek profile image
fischgeek

Curious, why are you so concerned with "context switches"? A function should only have knowledge of what it has been given.

Being able to pipe functions together is where the power (and fun) comes in! Have a look at F# For Fun and Profit for a reference on thinking functionally.

Also, see what they say specifically about currying.

I know these are referring to a different language, but the baisc principals remain. Hope this helps you! I'll try to come up with some "real-world" examples time permitting. Cheers.

Collapse
kalitine profile image
Philippe Kalitine

Thanks @alexdevero for a good and really detailed explanation of what currying is. New concept for me as an experienced JavaScript developer. While reading I was looking for a section like "The purpose of currying" or "What kind of problem it solves". I cannot figured out my self any cases where it's useful. After having read comments to this article I presume that one point is that carrying is improve the code readability. If so I would say it's quite opinionated argument.

Collapse
fischgeek profile image
fischgeek

Being able to pipe functions is a fantastic ability. Only those with functional backgrounds are going to fully understand what you've done here. Granted, js is not a functional language natively but this is an example of how one can leverage its flexability. Nice post.

Collapse
mastodonnine profile image
Mastodon9

Great post to a pretty complex topic in JavaScript

Collapse
fischgeek profile image
fischgeek

Nice post. Also, I was just made aware of this little repo: Functional programming in TypeScript!