What is Currying in JavaScript?

skaytech

skaytech

Posted on October 9, 2020

What is Currying in JavaScript?

Introduction

In this article, we will first look at what first-class citizens and higher-order functions are to lay the foundation to explain 'Currying' in JavaScript. The code samples provided along with the explanation should make it easy to follow and understand the concepts.

First-Class Citizens

In JavaScript, the functions are treated as 'First Class' citizens. What this means is that any function can be returned to another function, since a function is fundamentally an object.

Let us take a quick example to explain this better. The below code is an example of a simple function.

//A Simple Arrow Function returning a value '50'
const sum = () => {
  return 50;
};

//Invoke the function and display the value on the console.
console.log(sum()); //Output -> 50
Enter fullscreen mode Exit fullscreen mode

In the above example, the number 50 is returned when the function sum() is invoked.

As per the definition of a First-Class citizen, we can return the function sum() instead of the value 50 as shown in the code example below.

//Return the Function sum() instead of returning the value by adding the additional ()
const sum = () => () => {
  return 50;
};

//Invoke the function and display the value on the console.
console.log(sum());

/*
Output
-------
() => {
  return 50;
}
*/
Enter fullscreen mode Exit fullscreen mode

Higher-Order Functions

Higher-order functions are functions that take other functions as arguments or functions that return a function as their result.

The below code example will make the above explanation more clear.

//Callback Function - Returns the sum of a & b
const sum = function(a, b) {
    return a + b;
}

//Higher order function - takes 'func' as an argument & returns a 'func' for execution
const higherOrderFunction = function(func, a, b) {
    return func(a, b);
}

//Invoke the higherOrderFunction and pass 'sum' function as an argument with the digits 2 & 3
console.log(higherOrderFunction(sum, 2, 3)); //Output -> 5
Enter fullscreen mode Exit fullscreen mode

Things to note:

  • The function 'higherOrderFunction' accepts a function 'func' as a parameter.
  • The function 'func' that is passed in as a parameter is referred to as a callback.

Array.forEach, Array.map, Array.filter are some examples of high-order functions.

Currying

Currying a function is the process of taking a single function of multiple arguments and decomposing it into a sequence of functions that each take a single argument.

Let us take the following simple example:

//An Arrow function taking in arguments x & y and returning the sum
const sum = (x, y) => {
  return x + y;
};

//Output -> 5
console.log(sum(3, 2));

Enter fullscreen mode Exit fullscreen mode
//By applying Currying the same can be broken into a function returning another function
const sum = (x) => {
  return (y) => {
    return x + y;
  };
}; 

//Output -> 5
console.log(sum(3)(2));
Enter fullscreen mode Exit fullscreen mode

Using ES6 Arrow Functions, the above code can further be written in a simple manner as shown below.

//Simplified way to write the function using ES6 - Arrow Functions
const sum = (x) => (y) => x + y;

//Output -> 5
console.log(sum(3)(2));
Enter fullscreen mode Exit fullscreen mode

That's all there is to currying. Let us look at a practical use-case of where it can be applied.

A Practical Use-Case

Let us assume we have to read entries from a database of an e-commerce application that has the entities, user, product, and ratings.

To query a single product from the database, we can write a function 'getProductById' as shown below.

//Simplified way to write the function using ES6 - Arrow Functions
const getProductById = (connection, id) => {
    connection.select('products').where({ id })    
}

//Invoke the function getProductById by passing the connection object & the product Id
getProductById(connection, 1);
Enter fullscreen mode Exit fullscreen mode

By applying the 'currying' concept, we can simplify the above code as shown below.

//By applying Currying -> The function productById will return a function that'll query the products table by 'id'.
const getProductById = (connection) => (id) => {
    connection.select('products').where({ id })
}

//Invoke the function getProductById by passing the connection object & the product Id
const getProductByIdQuery = getProductById(connection);

/**
 * The Output of the above function 'getProductById' will be
 * 
 * (id) => {
 *    connection.select('products').where({ id })
 * }
 * 
 * and it will be assigned to getProductByIdQuery function
 */

//getProductByIdQuery can be simply invoked to fetch the product by it's Id
const product = getProductByIdQuery(1); //Ouput -> Return product matching the 'id' 1
Enter fullscreen mode Exit fullscreen mode

The advantages of the above approach:

  • The above approach obviously simplifies the code by avoiding the calling method to pass the 'connection' object repeatedly.
  • Further, the biggest advantage is that we can encapsulate the 'connection' object by modifying the access level to the getProductById() function as private. In simple words, nobody should know about the 'connection' object who is querying for products.

We can further apply the 'currying' concept to the above example and take it to the next level and make it even more generic, so that, you can query for products, users, and reviews table.

//By applying Currying -> The function productById will return a function that'll query the products table by 'id'.
const getConnection = (connection) => (table) => (id) => {
    connection.select(table).where({ id })
}

//While we initialize the application - Get the Database connection
const getTableQuery = getConnection(connection);

/**
 * The Output of the above function 'getConnection' will be
 * (table) => {
 *    (id) => {
 *        connection.select('products').where({ id })
 *     }
 * }
 * and it will be assigned to getTableQuery function
 */

//Pass the table name 'products' to get the 'getProductById' query
const getProductByIdQuery = getTableQuery('products');

/**
 * The Output of the above function 'getTableQuery' will be
 * 
 * (id) => {
 *    connection.select('products').where({ id })
 * }
 * 
 * and it will be assigned to getProductByIdQuery function
 */

//getProductByIdQuery can be simply invoked to fetch the product by it's Id
const product = getProductByIdQuery(1); //Ouput -> Return product matching the 'id' 1
Enter fullscreen mode Exit fullscreen mode

Now that we have found a way to generalize querying any table, querying users and reviews are as simple as the code shown below.

/**
* Pass the table name to get the 'getUserById' and 'getReviewById' query
*/
const getUserByIdQuery = getTableQuery('users');
const getReviewByIdQuery = getTableQuery('reviews');

//Fetch the user with the 'id' 5 using getUserByIdQuery
const user = getUserByIdQuery(5);

//Fetch the review with the 'id' 10 using getReviewByIdQuery
const review = getReviewByIdQuery(10);

Enter fullscreen mode Exit fullscreen mode

As you can see, using the above code simplifies things, promotes reuse, and overall encourages the use of encapsulation.


daily.dev delivers the best programming news every new tab. We will rank hundreds of qualified sources for you so that you can hack the future.
Daily Poster

💖 💪 🙅 🚩
skaytech
skaytech

Posted on October 9, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

What is Currying in JavaScript?
100daysofcode What is Currying in JavaScript?

October 9, 2020

Build a custom SPA Router using VanillaJS
100daysofcode Build a custom SPA Router using VanillaJS

August 11, 2020

Async Functions - Chapter 2: Promises
100daysofcode Async Functions - Chapter 2: Promises

June 24, 2020