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
  • Wow, Windsurf has just updated its new policy for free accounts, everyone. The three most notable points are:

    • 25 credits per month to use premium models like gpt-4, sonet 3.5...
    • Unlimited use of the Write mode (similar to Cursor's Agents) with their homegrown Cascade Base model.
    • Unlimited code suggestions, especially with always fast speed (previously limited to slow speed).

    It's worth coming back, right everyone? 🤤

    » Read more
  • Deep Research Mini has started appearing in my free GPT account. This can be considered a simplified version of the Deep Research feature, which is only available in paid GPT accounts.

    I just tried a command: 'The coffee consumption situation in Vietnam in 2024', and it went off to find and compile documents for 34 minutes, producing a pretty neat report. This feature seems quite useful; I need to use it more to know for sure 🤤

    Oh, it's called mini because it uses the 4o-mini model, and currently, you can only use it 5 times a month 🫣

    » Read more
  • R1 hasn't passed, R2 has already arrived 😅

    Although it's just rumors for now, only Deepseek seems to be making a similar impact as OpenAI or Anthropic. What users care about are quality & price 😄

    Deepseek R2 will be releasing soon

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