Functional pipe
Tags: functional, javascript, cleancode
Published: 2020-03-04 15:31:43

When we are building applications, we generally end up chaining the result of one function into another function, for example the following is not an uncommon thing to see:

```javascript
function purchasePriceReducer(previous, current) {
  previous.push(current.price);
  return previous;
}

function withTaxMapper(item) {
  const withTax = item * 1.2;
  return Math.round(withTax * 100) / 100;
}

function costReducer(previous, current) {
  return previous + current;
}

// dummy express handler example
app.post('/pay', async (req, res, next) => {
  // [
  //  { product_id: 103, price: 1.30 },
  //  { product_id: 3, price: 20.40 },
  //  { product_id: 29, price: 14.76 }
  // ]
  const purchases = req.body.purchases;
  const prices = reduce(purchases, purchasePriceReducer, []);
  const withTax = map(prices, withTaxMapper);
  const totalCost = reduce(withTax, costReducer, 0);
  await paymentService.init(totalCost);
  return res.status(200).send('OK');
});
```

> In the example above we make use of the [Array Map](https://dev.to/jamesrweb/array-map-4en) and [Array Reduce](https://dev.to/jamesrweb/array-reduce-2bfh) functions that we built in previous articles.

This is an example of something that is common in the wild. Namely, we import some helpers, take some content from a request and we do something to that content before sending back some sort of message or status to the requester. This kind of chaining is what we will address in today’s post by looking at functional pipes and how they can help us write clearer code in the grand scheme of things.

## Tests

```javascript
describe("Pipe", () => {
  it("Should throw for invalid parameters", () => {
    expect(() => pipe("string")).toThrowError(TypeError);
  });

  it("Should throw even if a sub array of functions is provided", () => {
    expect(() =>
      pipe(
        () => "first function",
        [
          () => "second function, inside array",
          () => "third function, inside array"
        ]
      )
    ).toThrowError(TypeError);
  });

  it("Should allow functions to be passed by reference", () => {
    const addOne = number => number + 1;
    const double = number => number * 2;
    const result = pipe(
      addOne,
      double
    )(5);

    expect(result).toBe(12);
  });

  it("Should allow anonymous functions to be passed", () => {
    const result = pipe(
      number => number + 1,
      number => number * 2
    )(5);

    expect(result).toBe(12);
  });

  it("Should return correctly when values are generated from sub pipes", () => {
    const addOne = number => number + 1;
    const double = number => number * 2;
    const result = pipe(
      addOne,
      double,
      number => pipe(
        addOne
      )(number)
    )(5);

    expect(result).toBe(13);
  });
});

describe("PipeWith", () => {
  it("Should return as expected", () => {
    const addOne = number => number + 1;
    const double = number => number * 2;
    expect(pipeWith(5, addOne, double)).toBe(12);
  });
});
```

Our tests check that parameter validation is run, pipes return as expected and sub-pipes are executed correctly. We also have the function `pipeWith` defined here as a helpful proxy to the `pipe` function by taking the value and then the function to run in order over that value just like the `pipe` function would but with immediate invocation to receive the resulting value immediately instead of delaying the `pipe` execution. 

You might be asking why the signature of `pipeWith` is not just the default behaviour of `pipe`. In short, this is because in some scenarios you will want to setup your pipeline in advance, especially if it is re-used in multiple areas of your application and you'd prefer to wait until later to put a value through that pipeline. The two functions existing alongside one another adds an extra layer of flexibility to match your needs and preferred coding style.

## Implementation

```javascript
/**
 * @function pipe
 * @description A function pipeline to apply over a given value
 * @param {Function[]} fns - The functions to call when a value is provided
 * @returns {Function} The function where the value to call the pipeline on is provided
 */
function pipe(...fns) {
  if(fns.every(fn => typeof fn === "function") === false) {
    throw new TypeError("All parameters should be of type Function. At least one parameter does not meet with this criteria.");
  }
  
  return input => reduce(fns, (prev, fn) => fn(prev), input);
}

/**
 * @function pipeWith
 * @description A function to apply a pipeline of functions to a given value
 * @param {*} value - The value to apply the pipeline to
 * @param {Function[]} fns - The functions to call when a value is provided
 * @returns {*} The result of the pipeline
 */
function pipeWith(value, ...fns) {
  return pipe(...fns)(value);
}
```

> In the example above we make use of the [Array Reduce](https://dev.to/jamesrweb/array-reduce-2bfh) function that we built in a previous article.

The implementation above provides us with 2 helper functions.

The first of these is the `pipe` function which takes a list of functions, returns another function which expects an `input` to be provided and then runs all the functions in order over the value passing the result of the previous function to the next via a reducer.

The second helper function is `pipeWith` which takes a `value` and the functions to apply to that `value` and simply returns the end result of the `pipe` pipeline.  I think this is a nicer interface to use but if you prefer to use pipe directly, that is totally good to do too.

Taking our opening example, we could alter it to do the following by piping smaller more manageable functions together:

```javascript
function purchasePriceReducer(previous, current) {
  previous.push(current.price);
  return previous;
}

function withTaxMapper(item) {
  const withTax = item * 1.2;
  return Math.round(withTax * 100) / 100;
}

function costReducer(previous, current) {
  return previous + current;
}

function getPricesFromPurchases(purchases) {
  return reduce(purchases, purchasePriceReducer, [])
}

function applyTaxes(prices) {
  return map(prices, withTaxMapper);
}

function sum(prices) {
  return reduce(prices, costReducer, 0);
}

// dummy express handler example
app.post('/pay', async (req, res, next) => {
  const totalCost = pipeWith(
    req.body.purchases, 
    getPricesFromPurchases,
    applyTaxes,
    sum
  );
  await paymentService.init(totalCost);
  return res.status(200).send('OK');
});
```

More than likely these helpers would be in external files and not with the router itself to show that I have created [a project for you to see an example setup](https://repl.it/repls/QuarrelsomePhysicalPhases) for the above code. In that example the code is way cleaner and looks like so:

```javascript
const { pipeWith } = require("./helpers/pipe");
const { sum } = require("./helpers/sum");
const { getPricesFromPurchases } = require("./helpers/purchase-prices");
const { applyTaxes } = require("./helpers/apply-taxes");

// dummy express handler example
app.post('/pay', async (req, res, next) => {
  const totalCost = pipeWith(
    req.body.purchases, 
    getPricesFromPurchases,
    applyTaxes,
    sum
  );
  await paymentService.init(totalCost);
  return res.status(200).send('OK');
});
```

In my opinion pipes are useful in a variety of cases and since they just take functions to apply to values, you can have pipes call other pipes and so on too which makes them a very powerful abstraction to make use of.

By using functional composition and our [Array Map](https://dev.to/jamesrweb/array-map-4en) and [Array Reduce](https://dev.to/jamesrweb/array-reduce-2bfh) to help with immutability, we are able to create a cleaner, simpler and more understandable structure to our code.

## Conclusions

Pipes are cool because in functional programming, we look at our code as the computation of actions running together. A pipeline like this shows how powerful the humble function really is and how they can literally feed one into the other to resolve the computation of any programme.

I hope this was an interesting article for you and that you found some value in it, how will you use pipes in your code? Let me know in the comments below!