Implementing more efficient code with compose & pipe functions in Javascript

Implementing more efficient code with compose & pipe functions in Javascript

Daily short news for you
  • Zod 4 has just been introduced with numerous performance improvements. For those who don't know, Zod is a data validation library focused on performance and a developer-friendly API.

    A quick look reveals speed increases of x7, x9, x17, x100... compared to the previous version 😅

    » Read more
  • Revealing how readers of 2coffee.dev will read articles in the future 🫣

    » Read more
  • Microsoft has decided to "transform" the Windows Subsystem for Linux (WSL) on Windows into an open-source project 👏

    Back when I was still into gaming on the computer, I absolutely hated Ubuntu. Probably because it couldn't run games. But programming with Linux kernels was such a delight. So I found myself in a dilemma. I wanted to use Linux but had to stick with Windows.

    Microsoft's release of WSL was like a necessary savior. Quite cool. It's simply using Linux commands on Windows. However, it's still not entirely "smooth" like a real operating system. Now that they’ve open-sourced it, I hope there will be even more improvements 🙏

    microsoft/WSL

    » Read more

Introduction to composition function

Note: Before continuing to read this article, I recommend that you learn about What is Curry function? A delicious "curry" dish and how to enjoy it? beforehand or, if you already know about curry function, you can continue reading the article.

Composition is a mechanism that combines multiple simple functions to build a more complex function. The result of each function will be passed to the next function.

It is similar to in mathematics where we have a function f(g(x)), which means the result of g(x) is passed to the function f. That's what composition is.

Let's take a simple example: write a function to perform the calculation 1 + 2 * 3.

For this calculation, we have to perform the multiplication first and then the addition. The code when implemented using functions in JavaScript will look like this:

const add = (a, b) => a + b;
const mult = (a, b) => a * b;
add(1, mult(2, 3));

Oh! the code runs fine but it's a bit messy, isn't it? What if I want to divide everything by 4 now? The code will look like this:

div(add(1, mult(2, 3)), 4);

It's even messier now!

Now let's move on to another example. Suppose I have a list of users consisting of names and ages, let's get the names of people over 18 years old. The code for that would look like this:

const users = [
  { name: "A", age: 14 },  
  { name: "B", age: 18 }, 
  { name: "C", age: 22 },  
];

const filter = (cb, arr) => arr.filter(cb);
const map = (cb, arr) => arr.map(cb);

map(u => u.name, filter(u => u.age > 18, users)); // ["C"]

The idea is that I will create 2 functions filter and map, where filter is used to filter and map is used to iterate through the elements. The code above works fine, but like the first example, it's a bit cumbersome.

So, is there a way to solve both of these issues? Or at least make the code clearer when the problem conditions become more complex?

Implementation

The compose function

My goal is to create a function that takes multiple parameters, which are smaller functions to perform a certain amount of work (Higher Order Function).
It will look like this:

compose(function1, function2…, functionN): Function

compose takes in functions and returns a function. The idea of compose is that when it is called, it will execute the functions in the parameter from right to left, where the result of the previous function will be passed as an argument to the next function.

Here is a simple way to implement the compose function in ES6:

const compose = (...functions) => args => functions.reduceRight((arg, fn) => fn(arg), args);

It would make me very happy if you understand what compose really does inside, but if you don't understand, please comment below the article. I will follow your comment!

Now let's go back to the original example, let's modify the code a bit:

const add = a => b => a + b;
const mult = a => b => a * b;

const operator = compose(add(1), mult(2));
const result = operator(3);

// Or we can even shorten it

const result = compose(add(1), mult(2))(3);

I have turned add and mult into curried functions, why? Because when I turn them into curry, I can use them as a function parameter in compose.

Alright, now what if I want to divide everything by 4?

const div = a => b => b / a;
const result = compose(div(4), add(1), mult(2))(3);

It's easy to read, isn't it? From left to right, perform multiplication by 2, then add 1, and finally divide by 4. It's like a flow, isn't it?

Similarly, with example 2, let's modify its code a bit:

const users = [
  { name: "A", age: 14 },  
  { name: "B", age: 18 }, 
  { name: "C", age: 22 },  
];

const filter = cb => arr => arr.filter(cb);
const map = cb => arr => arr.map(cb);

compose(
  map(u => u.name),  
  filter(u => u.age > 18),  
)(users); // ["C"]

We can add more consecutive functions inside compose and still maintain the working data flow, and most importantly, keep the code relatively easy to read.

The pipe function

Similar to compose, pipe has the same idea as compose except that the order of executing functions in the parameter is from left to right.

The pipe function will be implemented like this:

const pipe = (...functions) => args => functions.reduce((arg, fn) => fn(arg), args);

Applying pipe to example 1:

const add = a => b => a + b;
const mult = a => b => a * b;

const result = pipe(mult(2), add(1))(3);

As you can see, the parameters are passed in reverse compared to compose, it will perform the functions from right to left: multiply 3 by 2, then add 1.

You can use compose and pipe depending on your preference or habit as both functions produce similar results.

Conclusion

Both compose and pipe functions, though small, bring great benefits in applying them to problems of processing continuous data. They help the code to be clearer and easier to read.

Most JavaScript libraries that support data processing already have similar functions like compose or pipe, such as _.compose, _.pipe in lodash, or compose, pipe in ramdajs.

I hope that after this article, you will have another method to process data in upcoming projects. If you encounter similar problems now, try applying it immediately!

Premium
Hello

The secret stack of Blog

As a developer, are you curious about the technology secrets or the technical debts of this blog? All secrets will be revealed in the article below. What are you waiting for, click now!

As a developer, are you curious about the technology secrets or the technical debts of this blog? All secrets will be revealed in the article below. What are you waiting for, click now!

View all

Subscribe to receive new article notifications

or
* The summary newsletter is sent every 1-2 weeks, cancel anytime.

Comments (0)

Leave a comment...