compose
Prompt
Implement a compose function that takes any number of functions and returns a new function. When the returned function is called with an argument, it applies the given functions from right to left, passing the result of each to the next.
If no functions are passed, the returned function should return its input unchanged.
Playground
compose is the mirror of pipe. If you've solved
pipe, the only difference is the direction: pipe goes
left to right, compose goes right to left.
Array.reduceRight() works exactly like reduce() but
iterates from the last element to the first. That's
exactly the direction compose needs. Alternatively, you
can use a for loop that starts from the end.
Solution
Explanation
If you've already solved the pipe question, compose is nearly identical. The only difference is the direction:
pipe(a, b, c)(x)applies functions left to right: firsta, thenb, thenccompose(a, b, c)(x)applies functions right to left: firstc, thenb, thena
So compose(addOne, double)(5) means: run double first (because it's on the right), then run addOne on the result. double(5) gives 10, addOne(10) gives 11.
The code
function compose(...fns) {
return function (value) {
return fns.reduceRight(
(result, fn) => fn(result),
value
);
};
}compose collects all the functions into the fns array using ... (rest parameters), then returns a new function. When that new function is called with a value, it uses reduceRight to process the functions from the last one to the first.
If you haven't used reduceRight before, it works exactly like reduce, but starts from the end of the array instead of the beginning. It takes two arguments: a callback and an initial value. Here, the initial value is value (whatever the caller passed in). It hands that to the rightmost function, takes the result, hands it to the next function to the left, and keeps going until every function has been applied.
Walking through an example
compose(addOne, double)(5);The functions array is [addOne, double]. reduceRight starts from the right:
- Start with
value = 5 - Apply
double(5)→10 - Apply
addOne(10)→11
Result: 11.
If no functions are passed, reduceRight on an empty array simply returns the initial value unchanged, which is exactly the behavior we want.
Alternative with a for loop
If reduceRight doesn't come to mind, a for loop starting from the end does the same thing:
function compose(...fns) {
return function (value) {
let result = value;
for (let i = fns.length - 1; i >= 0; i--) {
result = fns[i](result);
}
return result;
};
}Both approaches are correct. The reduceRight version is more concise, but the for loop is easier to read if you're not familiar with reduceRight.
pipe is more commonly asked in interviews than
compose, but both test the same concept: chaining
functions together. Libraries like Lodash provide _.flow
(pipe) and _.flowRight (compose). Redux's compose
utility uses this exact pattern to combine middleware.