Сurrying 101: A Quick Guide for JavaScript Devs

Written by marat | Published 2021/10/28
Tech Story Tags: javascript | currying | functional-programming | redux | middleware | composition | react | javascript-tutorial

TLDRCurrying is a method of creating functions with one or several pre-defined arguments. The essence of currying is simple: to take a function of any arity (number of expected arguments) and make it into a unary function. JavaScript's syntax doesn't lend itself well to such functions, which is why, when it comes to JavaScript, implementations of the curry function tend to be less strict and allow to pass several arguments. via the TL;DR App

Currying is one of the core concepts of functional programming. This article provides a brief overview of what it is and how and why it’s used in JavaScript.

What is currying of a function?

Currying is a method of creating functions with one or several pre-defined arguments. The essence of currying is simple: to take a function of any arity (number of expected arguments) and make it into a unary function.

In the simplest case, a function like this:

(a, b) => a + b

once curried, it looks like this:

(a) => (b) => a + b

This is an example of strict currying, which always results in the function becoming unary. This is the way it is implemented in, for example, santuary.js.

Functional programming languages tend to use strict currying, but JavaScript's syntax doesn't lend itself well to such functions, which is why, when it comes to JavaScript, implementations of the curry function tend to be less strict and allow to pass several arguments.

Let's look at a few examples.

Example 1: simple case

Let's say we have a list of objects, and we need to create a function to retrieve some property from each item on the list.

Here is how it would be realized under the common imperative approach:

const list = [
  { id: 1, name: 'Alberta' },
  { id: 2, name: 'Ashlee' },
  { id: 3, name: 'Richards' }
];

const getIds = (list) => {
  return list.map((item) => {
    return item.id;
  })
}

const ids = getIds(list); // [ 1, 2, 3 ]

Now let's see how we can make it better using function currying.

First, let's implement two auxiliary functions to handle the data:

// The function returns the value of a property of an object
const pick = (field) => (obj) => obj[field];

// Wrapper over Array.prototype.map
const map = (fn) => (array) => array.map(fn);

Nothing special -, just a curried getter function for object properties and a curried version of the map method.

With these functions, our code becomes much more concise:

const list = [
  { id: 1, name: 'Alberta' },
  { id: 2, name: 'Ashlee' },
  { id: 3, name: 'Richards' }
];

const getIds = map(pick('id'));

const ids = getIds(list); // [ 1, 2, 3 ]

With this approach, instead of coding a specific logic for handling specific data, as it usually happens with the imperative approach, we code recipes by combining functions.

Example 2: generic solution

The implementation from Example 1 is not universal. It is possible to make a function that itself will do the currying, with any other function being passed as the argument:

function curry(fn) {
    // fn - the function to be curried
    const length = fn.length;

    return function currify() {
        const context = this;

        // args - stores the arguments of the inner function
        const args = Array.prototype.slice.call(arguments);

        if(args.length >= length) {
            return fn.call(context, ...args)
        } else {
            return currify.bind(context, ...args);
        }
    }
}

Where could this approach be applied? If you have a function that is repeatedly being called with essentially the same arguments, it would be a good candidate for currying. You can create a new function using the partial application method on the original function. The new function will store recurring parameters. This way, you won't have to pass arguments every time.

Example 3: Redux middleware

Currying can also be done in Redux middleware.

Middleware is a function that takes store and returns another function that accepts callback with next as the argument. It then returns a third function that accepts the Redux action. It is this last function that represents the implementation of the middleware:

export default function(store) {
  return function(next) {
    return function(action) {

    }
  }
}

In this case, we can easily configure a mediator - if all arguments were contained in one function:

function(store, next, action) { … }

We would have to pass them each time we use the mediator. But thanks to currying, we can call the first function once and get a return value that "remembers" which store to use. The same applies to the next argument.

Example 4: Higher Order Component

Currying is also used in the Higher Order Components method. In a nutshell, it's a function that takes a component as an argument and returns a wrapped version of it, which is also a component. HOCs can often be found in third-party libraries, such as the connect function in Redux. The connect function accepts arguments and returns another function, which accepts as a parameter the following component:

connect(mapStateToProps, mapDispathToProps)(Component)

What are the advantages of currying?

Currying can be used to break down complex functions with many arguments – this makes it easy to reuse code.

Another and more significant advantage of curried functions is the fact that they can be used in function composition.

We shall not delve into that right now, but in short, function composition allows us to pass the result of the call of the previous function as an argument. In other terms:

compose(f, g) -> g(f())

Currying and composition allow building complex logic with simple functions when working in tandem.


Written by marat | Hi guys, I'm Marat, a software developer with expertise in JavaScript, TypeScript and React
Published by HackerNoon on 2021/10/28