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
  • I just discovered the idb-keyval library that helps implement a key-value database simply. As shared in the series of posts about the process of making OpenNotas, I was struggling to find a type of database for storage, and it seemed quite difficult; in the end, I settled on localForage.

    idb-keyval is quite similar to localForage, but it seems to be doing a little better. For example, it has an update function to update data, simply imagine it like this:

    update('counter', (val) => (val || 0) + 1);

    Unlike the set function, which completely replaces the data.

    » Read more
  • At the beginning of the new year, may I share the amount earned after 1 month of advertising at indieboosting.com 🥳🥳🥳

    » Read more
  • Since the Lunar New Year holiday has started, I won't be posting anymore. See you all after the holiday! 😁

    » 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

Me & the desire to "play with words"

Have you tried writing? And then failed or not satisfied? At 2coffee.dev we have had a hard time with writing. Don't be discouraged, because now we have a way to help you. Click to become a member now!

Have you tried writing? And then failed or not satisfied? At 2coffee.dev we have had a hard time with writing. Don't be discouraged, because now we have a way to help you. Click to become a member 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...
Scroll or click to go to the next page