paint-brush
Function Composition (Pipe, Compose) in JavaScript Explained by@marat
1,162 reads
1,162 reads

Function Composition (Pipe, Compose) in JavaScript Explained

by MaratNovember 26th, 2021
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Functional programming is about developing small simple functions and combining them for the purposes of executing more complex tasks. This process is called composition (or function superposition) It is a pinpoint application of one function to the result of another one. There is no function composition function in the native JavaScript or React. It can be obtained with the aid of third-party libraries. It is present in Redux. It allows for more convenient wrapping of components around other components, which includes using a high-order component.

Company Mentioned

Mention Thumbnail

Coin Mentioned

Mention Thumbnail
featured image - Function Composition (Pipe, Compose) in JavaScript Explained
Marat HackerNoon profile picture

Functional programming is about developing small simple functions and combining them for the purposes of executing more complex tasks.


As the program’s logic is divided into small functions under the functional approach, they have to be assembled into larger functions for the application to work. This process is called composition.


Function composition (or function superposition) is a pinpoint application of one function to the result of another one, or in other words, the use of one function’s result as the argument of another one.


For example:


function1(function2(value))


Each time, the result of the right-hand function in the chain is passed as a parameter to the function located in the left-hand side. Therefore, all the functions in the chain can have only one parameter, except for the rightmost one which is executed first.

Why is function composition needed?

Function composition has the following advantages:


  1. It shortens the code
  2. We can see the order of actions at once
  3. A link can be easily removed from the chain of functions
  4. If we consider React, it allows for more convenient wrapping of components around other components, which includes using a high-order component.

How do we use it?

There is no сompose function in the native JavaScript or React. It can be obtained with the aid of third-party libraries. For example, it is present in Redux.


The syntax of its application looks like this:


compose(function1, function2)(function3);


Here, function3 will be executed first. And then one by one, from right to left, its result will be accepted and processed by funсtion2 and function1.


To obtain a more universal solution, we can use the reduceRight method:


const compose = (...fns) => (initialVal) =>
  fns.reduceRight((val, fn) => fn(val), initialVal);


Here are a few examples of how they can be used:


const add2 = (n) => n + 2;
const times2 = (n) => n * 2;

const times2add2 = compose(add2, times2);
const add2times2 = compose(times2,add2);
const add6 = compose(add2, add2, add2);

times2add2(2); // 6
add2tiems2(2); // 8
add6(2); // 8


You might think that it has no relation to front-end, but it is also helpful in SPA (single page application). For example, you can add behavior to the React component using higher-order functions:


function logProps(InputComponent) {
  InputComponent.prototype.componentWillReceiveProps = function (nextProps) {
    console.log('Current props: ', this.props);
    console.log('Next props: ', nextProps);
  };

  return InputComponent;
}

// EnhancedComponent will log whenever props are received
const EnhancedComponent = logProps(InputComponent);

And what about pipe?

It is customary to use the abstraction tool called pipe() for composition. With the aid of pipe we obtain a declarative code, that is, the programmer describes the result and not the implementation.


Let’s consider the difference between the declarative and imperative approaches in more detail. Let’s assume there are 2 simple functions:


const g = n => n + 1;
const f = n => n * 2;


We want to combine them into one function, so that first one would be added to the number, and then the obtained result would be multiplied by 2.


With the imperative approach, the function’s implementation would look like this:


const doImperative = x => {
  const afterG = g(x);
  return f(afterG);
};

doImperative(20); // 42


The declarative approach suggests that the auxiliary function pipe() should be used. It is implemented by many popular libraries, such as lodash, but in actual fact its primitive implementation is quite simple and can fit into one line of code:


const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);


Now, with the pipe() function, the example above can be rewritten using the declarative approach:


const doDeclarative = pipe(g, f)
doDeclarative(20); // 42


The difference between the approaches becomes even more obvious when the task gets more complicated. Let’s add logging after each step with the aid of the function:


const trace = message => value => {
  console.log(`${message}: ${value}`);
  return value;
}


The imperative style:


const doImperative = x => {
  const afterG = g(x);
  trace('after g')(afterG);
  const afterF = f(afterG);
  trace('after f')(afterF);
  return afterF;
};

doImperative(20);


The declarative style:


const doDeclarative = pipe(
  g,
  trace('after g'),
  f,
  trace('after f')
);
doDeclarative(20);


The doDeclarative() method doesn’t store the intermediary result in the variables, the result of the previous function is passed to the next one instead. The pipe function is responsible for that.


It is interesting to note that the chain of Promises is a composition too:


Promise.resolve(20)
  .then(g)
  .then(trace('after g'))
  .then(f)
  .then(trace('after f'));


Each subsequent then block accepts the previous one’s result and passes it to the next one. Looks very much like the previously considered variant of pipe, doesn’t it?

Conclusion

Function composition improves code readability. Instead of function nesting, you can link the functions into a chain and create higher-order functions with meaningful names.


The implementation of compose is present in many JavaScript utility libraries (lodash, ramda and so on).