JS Functional Concepts: Pipe and Compose

JoelBonetR 🥇 - Jan 2 '23 - - Dev Community

Function piping and composition are concepts from functional programming that of course are possible in JavaScript -as it's a multi-paradigm programming language-, let's deep into this concepts quickly.

The concept is to execute more than a single function, in a given order and pass the result of a function to the next one.

You can do it ugly like that:



function1(function2(function3(initialArg)))


Enter fullscreen mode Exit fullscreen mode

Or using function composition



compose(function3, function2, function1)(initialArg);


Enter fullscreen mode Exit fullscreen mode

or function piping



pipe(function1, function2, function3)(initialArg);


Enter fullscreen mode Exit fullscreen mode

To make it short, composition and piping are almost the same, the only difference being the execution order; If the functions are executed from left to right, it's a pipe, on the other hand, if the functions are executed from right to left it's called compose.

A more accurate definition would be: "In Functional Programming, Compose is the mechanism that composes the smaller units (our functions) into something more complex (you guessed it, another function)".

Here's an example of a pipe function:



const pipe = (...functions) => (value) => {
    return functions.reduce((currentValue, currentFunction) => {
      return currentFunction(currentValue);
    }, value);
  };


Enter fullscreen mode Exit fullscreen mode

Let's add some insights into this:

Basics

  • We need to gather a N number of functions
  • Also pick an argument
  • Execute them in chain passing the argument received to the first function that will be executed
  • Call the next function, adding as argument the result of the first function.
  • Continue doing the same for each function in the array.


/* destructuring to unpack our array of functions into functions */
const pipe = (...functions) => 
  /* value is the received argument */
  (value) => {
    /* reduce helps by doing the iteration over all functions stacking the result */
    return functions.reduce((currentValue, currentFunction) => {
      /* we return the current function, sending the current value to it */
      return currentFunction(currentValue);
    }, value);
  };


Enter fullscreen mode Exit fullscreen mode

We already know that arrow functions don't need brackets nor return tag if they are returning a single statement, so we can spare on keyboard clicks by writing it like that:



const pipe = (...functions) => (input) => functions.reduce((chain, func) => func(chain), input);


Enter fullscreen mode Exit fullscreen mode

How to use



const pipe = (...fns) => (input) => fns.reduce((chain, func) => func(chain), input);

const sum = (...args) => args.flat(1).reduce((x, y) => x + y);

const square = (val) => val*val; 

pipe(sum, square)([3, 5]); // 64


Enter fullscreen mode Exit fullscreen mode

Remember that the first function is the one at the left (Pipe) so 3+5 = 8 and 8 squared is 64. Our test went well, everything seems to work fine, but what about having to chain async functions?

Pipe on Async functions

One use-case I had on that is to have a middleware to handle requests between the client and a gateway, the process was always the same (do the request, error handling, pick the data inside the response, process the response to cook some data and so on and so forth), so having it looking like that was a charm:



export default async function handler(req, res) {
  switch (req.method) {
    case 'GET':
      return pipeAsync(provide, parseData, answer)(req.headers);
     /* 
       ... 
     */ 


Enter fullscreen mode Exit fullscreen mode

Let's see how to handle async function piping in both Javascript and Typescript:

JS Version



export const pipeAsync =
  (...fns) =>
  (input) =>
    fns.reduce((chain, func) => chain.then(func), Promise.resolve(input));


Enter fullscreen mode Exit fullscreen mode

JSDoc Types added to make it more understandable (I guess)



/**
 * Applies Function piping to an array of async Functions.
 * @param  {Promise<Function>[]} fns
 * @returns {Function}
 */
export const pipeAsync =
  (...fns) =>
  (/** @type {any} */ input) =>
    fns.reduce((/** @type {Promise<Function>} */ chain, /** @type {Function | Promise<Function> | any} */ func) => chain.then(func), Promise.resolve(input));


Enter fullscreen mode Exit fullscreen mode

TS Version



export const pipeAsync: any =
  (...fns: Promise<Function>[]) =>
  (input: any) =>
    fns.reduce((chain: Promise<Function>, func: Function | Promise<Function> | any) => chain.then(func), Promise.resolve(input));


Enter fullscreen mode Exit fullscreen mode

This way it will work both for async and non-async functions so it's a winner over the example above.

You may be wondering what about function composition, so let's take a gander:

Function Composition

If you prefer to call the functions from right to left instead, you just need to change reduce for redureRight and you're good to go. Let's see the async way with function composition:



export const composeAsync =
  (...fns) =>
  (input) =>
    fns.reduceRight((chain, func) => chain.then(func), Promise.resolve(input));


Enter fullscreen mode Exit fullscreen mode

Back to the example above, let's replicate the same but with composition:

How to use



const compose = (...fns) => (input) => fns.reduceRight((chain, func) => func(chain), input);

const sum = (...args) => args.flat(1).reduce((x, y) => x + y);

const square = (val) => val*val; 

compose(square, sum)([3, 5]); // 64


Enter fullscreen mode Exit fullscreen mode

Note that we reversed the function order to keep it consistent with the example at the top of the post.

Now, sum (which is at the rightmost position) will be called first, hence 3+5=8 and then 8 squared is 64.


If you have any question or suggestion please comment down below

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .