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
  • A few days ago, Apple introduced a container tool developed by themselves and open-sourced as apple/container. It is used to create and run Linux containers as lightweight virtual machines on Mac, optimized for the Silicon chip line. Does that sound familiar? Kind of like Docker, right? 😄

    Docker on Mac needs to run a Linux virtual machine to use containers, and now with Apple introducing this, maybe Docker will see an update soon. We don’t know how the performance will be yet; we’ll have to wait for evaluations from experts. It’s neither Go, nor Rust, nor C... apple/container is written in... Swift. Wow, that’s quite astonishing. 😳

    » Read more
  • Before, I mentioned that github/gh-copilot is a command-line tool that suggests commands or explains them. While browsing through some "comments," I discovered a rather interesting way of using it that I hadn't expected. That is to set an alias for the command gh copilot suggest as "??" and "???" and then, after getting used to it, it becomes extremely convenient. For example, if you want to compress a directory into a tar.gz format but forget the syntax, you can simply do: bash $ ?? "compress the pages directory into .tar.gz" Or if you encounter a command that is hard to understand, you can use: bash $ ??? "tar -xzvf pages.tar.gz pages"

    » Read more
  • A very nice website that aggregates CLI command line tools that I stumbled upon: terminaltrove.com

    It has a ranking system and is categorized, making it very easy to explore by topics that interest you, feel free to study it 😄

    » 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...