Here it is:
const compose = (...funcs) => funcs.reduce((previous, current) => x => current(previous(x)));
First, the spread operator here has the effect of accepting all the arguments as a single array, allowing us to use array methods on function’s parameters.
const compose = (...funcs) => // return composed function
So later, we won’t have to pass an array of functions, and can just pass an arbitrary number of parameters:
const timesTwo = x => x * 2; const timesThree = x => x * 3; const timesTwelve = compose(timesTwo, timesTwo, timesThree);
Since the compose function must itself return a function, we need to pass a callback function to the reduce method that returns a function:
const callback = (previous, current) => x => current(previous(x)); funcs.reduce(callback);
Previous will start as funcs and current as funcs, but previous will accumulate each function in the array with each call of the callback function:
previous = x => funcs(funcs(x)); // first run... previous = x => funcs(previous(x)); // second run... previous = x => funcs(previous(x)); // third run...
Once it’s reached the end of the array, you have a function that contains calls to each function in the original array, with the very first function call containing your initial x. By returning functions that accept an x with each callback, you ensure that the x can be passed all the way back to the first function call.
For composition to work, the functions must all be unary (i.e. the must all take just one parameter). This is why curried functions are so useful - they make it much easier to compose functions together.
With compose, you can then create functions by simply stringing other functions together:
let phrase = "No imperative code examples here!"; const dropLastWord = s => s.split(" ").slice(0, -1).join(" "); const firstLetterOfEachWord = s => s.split(" ").map(w => w).join(" "); const removeSpaces = s => s.split(" ").join(''); const decodeMessage = compose( dropLastWord, firstLetterOfEachWord, removeSpaces ); decodeMessage(phrase) // Nice
And also: if you prefer to have functions evaluated from right to left, you can use reduceRight instead:
const compose = (...funcs) => funcs.reduceRight((previous, current) => x => current(previous(x)));