The Array.reduce Method Part 3 - Making a Pipe Operation

Published: Monday, May 30, 2022

Greetings, friends! This is Part 3 of my tutorial series on the Array.reduce method in JavaScript! In this tutorial, I will discuss how to create your very own pipe operation.

Pipes

Before we dive into code, it's important to recognize what a "pipe" is. If you've ever done any plumbing, then you've likely worked with pipes. Or, perhaps you learned about pipes in games like Super Mario Bros. 🍄

Regardless of where you learned about pipes, you should know that there's an input and an output. We put something in and get something out. In Unix pipelines, we can use the Pipe character, |, to take the output of one command and "pipe" it into the input of another command. The | character can be found on many keyboards, usually above your "enter" or "return" key.

Pipe Operators

In some programming languages such as Elixir, a pipe operator is very common. Elixir uses |> as the pipe operator.

elixir
Copied! ⭐️
defmodule Math do
  def double(num) do
      num * 2
  end

  def triple(num) do
      num * 3
  end
end

result = 5 |> Math.double |> Math.triple
IO.puts result # 30

I hope it's clear what the pipe operator is doing. We start with a value of 5 and then pipe it through the Math.double method to get a value of 10. Then, we pipe this new value into Math.triple to get a final value of 30.

In JavaScript, we don't have a pipe operator...yet. There is a proposal to add a pipe operator to the JavaScript language, but it may be a while before this actually happens.

For now, we have to call multiple functions:

javascript
Copied! ⭐️
const double = num => num * 2;
const triple = num => num * 3;

const result = triple(double(5));
console.log(result); // 30

Calling a function around another function like this is known as function composition. It's not pretty, but it gets the job done. Until we have a pipe operator, |>, that we can use in JavaScript, we'll have to create our own pipe operation.

Pipe Operation Using Reduce

Our goal is to create a pipe function that will accept a list of callback functions and return a result. It should look like the following:

javascript
Copied! ⭐️
const double = num => num * 2;
const triple = num => num * 3;

const doubleAndTriple = pipe(double, triple);

const result = doubleAndTriple(5);
console.log(result); // 30

Our main task is to create the pipe function using the reduce method. Luckily, MDN already provides a nice implementation for us.

javascript
Copied! ⭐️
const pipe = (...functions) => initialValue => functions.reduce(
  (acc, fn) => fn(acc),
  initialValue
)

We're not done yet though. Copying and pasting code means very little if we don't know how it works. It's my job as the lecturer to explain how this pipe implementation works.

It's best to be familiar with arrow functions to really understand this implementation of the pipe function. We see that this pipe function is a function that returns a function that returns a result from a call to the reduce method. Essentially, we could have expanded the pipe method to the following:

javascript
Copied! ⭐️
function pipe(...functions) {
  return function(initialValue) {
    return functions.reduce(
      (acc, fn) => fn(acc),
      initialValue
    )
  }
}

Let's look at the parameter passed into the pipe function which is ...functions. The three dots mean something different in JavaScript depending on the context. When you see these three dots, it means you are either working with the rest parameter syntax or the spread syntax.

In the pipe method, we are using the rest parameter syntax. This syntax allows a function to accept an indefinite number of arguments as an array. Let's look at an example.

javascript
Copied! ⭐️
function convertToArray(...anyNumberOfArguments) {
  return anyNumberOfArguments; // returns an array due to rest parameter syntax
}

console.log(convertToArray(1, 2, 3));
// OUTPUT: [1, 2, 3]

As you can see, we can convert a list of arguments into a single array containing those arguments using the rest parameter syntax.

When we use the pipe method in our original code, we see that it works perfectly.

javascript
Copied! ⭐️
const double = num => num * 2;
const triple = num => num * 3;

const pipe = (...functions) => initialValue => functions.reduce(
    (acc, fn) => fn(acc),
    initialValue
)

const doubleAndTriple = pipe(double, triple);

const result = doubleAndTriple(5);
console.log(result); // 30

We are using the rest parameter syntax to create an array of functions. In this case, we are creating an array containing double and triple. We then return a function that accepts initialValue, so we can pass a starting value to the reduce method. We call the reduce method on the array of functions containing double and triple. During each iteration of the reduce method, a function will be called.

Here is a tabular breakdown of what happens during each iteration of the reduce method inside the pipe function:

iterationaccumulatorcurrent valuereturn value
1st5double function10
2nd10triple function30

Function Composition vs Pipes

You're probably wondering about the main benefit of using pipes over function composition. Imagine if we had to perform a lot of operations. Composing functions would get confusing:

javascript
Copied! ⭐️
const result = double(triple(double(triple(triple(5)))))

Instead, we can use the pipe function to make the code much more readable:

javascript
Copied! ⭐️
const result = pipe(triple, triple, double, triple, double);

Notice that it's much easier to tell the correct order of operations as well. Using the pipe method makes it clear about the correct order of operations. With function composition, it appears reverse of what is actually happening.

Conclusion

We have now seen an advanced use case of the reduce method in JavaScript. We can use reduce to create custom pipe functions that perform pipe operations. The pipe function helps us create more readable code. Until the pipe operator, |>, is added to JavaScript, we can use our custom pipe function instead.

Resources