My favorite ways to write pipe and compose in JavaScript

Yazeed Bzadough
May 16, 2018 · 3 min read

compose, and especially pipe, are easily among my favorite functions.

This article’s just to have fun and explore different implementations of these two gems. I recommend you understand what they do before reading this. Perhaps check out my deep-dive here.

pipe = (...fns) => x => fns.reduce((v, f) => f(v), x)

Classic.

Starting with the leftmost function, reduce an array of functions to a single value by calling the next function with the previous one’s output.

double = (x) => x * 2
add1 = (x) => x + 1
pipe(double, add1)(100) // 201

I discovered this implementation through Eric Elliott and wrote a deep-dive on it here.

Use reduceRight to implement compose. Now your functions are called from right, to left.

compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x)compose(double, add1)(100) // 202

You could also reverse fns and keep using reduce (less performant).

compose = (...fns) => x => (
fns
.reverse()
.reduce((v, f) => f(v), x)
)
compose(double, add1)(100) // 202

reverse mutates the array, though, so you might copy it first (even less performant).

compose = (...fns) => x => (
[...fns]
.reverse()
.reduce((v, f) => f(v), x)
)
compose(double, add1)(100) // 202

Use reduceRight to go back to pipe.

pipe = (...fns) => x => (
[...fns]
.reverse()
.reduceRight((v, f) => f(v), x)
)
pipe(double, add1)(100) // 201

But They’re All Unary

All the above snippets, by the way, are unary. Each function may only accept a single argument.

If your pipeline’s first function must be nAry (accepting n arguments), try this implementation:

multiply = (x, y) => x * y;
pipe = (...fns) => fns.reduce((f, g) => (...args) => g(f(...args)));
pipe(multiply, add1)(10, 10) // 101
// Takes multiple args now

This snippet’s from 30secondsofcode.org. Your first (leftmost) function may accept n arguments–all others must be unary.

Again, reduceRight gives us compose. Now your rightmost function may accept n arguments. Let’s move multiply to the end of the chain.

compose = (...fns) => fns.reduceRight((f, g) => (...args) => g(f(...args)));compose(add1, multiply)(10, 10) // 101
// Takes multiple args now
// Put multiply first

Like before, you could reverse the fns array and keep using reduce:

compose = (...fns) => (
[...fns]
.reverse()
.reduce((f, g) => (...args) => g(f(...args)))
);
compose(add1, multiply)(10, 10) // 101

If you want to keep reduce without the slight performance hit, just switch g and f:

compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)));compose(add1, multiply)(10, 10) // 101

And use reduceRight to switch back to pipe.

pipe = (...fns) => fns.reduceRight((f, g) => (...args) => f(g(...args)));pipe(multiply, add1)(10, 10) // 101
// put multiply first now

Conclusion

Phew! That’s a lot of ways to pipe and compose!

It just proves that, no matter what, you must loop over an array of functions, calling the next one with the previous one’s result.

It doesn’t matter if you use reduce, reduceRight, switch the invocation order, or whatever else.

If you want pipe(), go left-to-right. Want compose()? Go right-to-left.

Plain and simple. Until next time!

Take care,
Yazeed Bzadough

freeCodeCamp.org

This is no longer updated.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store