The Array.reduce Method Part 3 - Making a Pipe Operation
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.
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:
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:
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.
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:
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.
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.
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:
iteration | accumulator | current value | return value |
---|---|---|---|
1st | 5 | double function | 10 |
2nd | 10 | triple function | 30 |
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:
const result = double(triple(double(triple(triple(5)))))
Instead, we can use the pipe
function to make the code much more readable:
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.