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
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;
}
*/
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
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));
//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));
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));
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);
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
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
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);
As you can see, using the above code simplifies things, promotes reuse, and overall encourages the use of encapsulation.
Conclusion
I hope you enjoyed the article. Do connect with me on Twitter @skaytech.