Creating an ES6ish Compose in Javascript

Drew Tipson
7 min readJan 18, 2016

I actually started this article out intending to discuss a javascript implementation of functional Lenses. But I quickly realized that since I plan to brag about how cool it is that Lenses compose, I’m obviously going to need to both build and explain a nice slice-n-dice version of a standard compose function built with all the ES6 trimmings. So first I needed to write this article.

Now, we could always just use the excellent R.compose or _.flowRight from Ramdajs or lodash v4. But then there would be nothing to explain. And it’s fun to explain things!

Anyhow, I’ve mentioned compose before, and hopefully most functional fans are familiar with it, but here’s a refresher on the basic version:

const compose = (f1, f2) => value => f1( f2(value) );

Basically, we’re just nesting functions inside each other so that when they’re called with a final value, the result explodes outwards through each layer.

For my talk about Lenses, however, I’ll actually need the more flexible gold-star version of compose. That is, a compose that’s fully variadic. That is, one that can take an arbitrarily long list of functions and then work right to left (inner to outer) to create a composition of all those functions (i.e. return a new function).

(f1, f2, f3, f4…) => value => f1( f2(f3(f4(value) )));

Since functions can only return a single value, all these functions will be assumed to be unary…except, and here’s a tricky bit, the very FIRST function (the deepest inside). It should be able to accept any number of arguments (i.e. it can be any arity, from nullary to variadic), even though it will still return a single result like all the rest of its fellows.

At this point, let’s think about the overall structure of this kick-ass compose function we plan to build. It’ll go something like this:

Let’s ignore the magic bit for a second and just focus on how the structure of the arguments can and should work. There are two groups of arguments to deal with: the functions and the actual arguments that kick everything off (and will get passed to the first, innermost function). We’ve used a neat bit of ES6's Arrow function syntax to express that:

() => () => result;

Basically, that’s a cleaner version of this verbose mess:
function(){return function(){return result;};}

So, what about the form of the actual arguments in each step?

In ES5, we probably wouldn’t bother to name ANY of the arguments in the first step: to achieve a varidiac function, we’d be stuck using the arguments keyword to get them all as an array (and we’d have to be careful not to use arguments directly, as that’s a performance-killer).

With ES6, as you probably know, we have a much nicer way of dealing with that: (…fns) Which means that we can just reference fns in the body of our functions to get the complete list of arguments.

But that’s not quite what we wanted! We wanted the first argument used (which is to say, the last argument passed into the function) to get special treatment. Maybe there’s a neat way to specify the arguments such that that’s so right off the bat? Maybe…

(…fns, last_fn)

Wow, isn’t it cool that ES6 allows us to do that?! Haha, well no. It doesn’t. That actually won’t work. And I’ll bet you know why.

Because that …fns syntax has a name: it’s a rest parameter. Ah. It’s a way at getting at the “rest” of something. So it’d sort of make no sense for it to come first and then any other additional thing to come after “the rest.” What we can do instead is (fn, …fns) but that’s also not what we want. Or is it?

Well, maybe. Compose has a twin. It’s often called pipe. It’s just compose, but run in reverse, meaning that it’s the first argument passed that is innermost and gets the special treatment (i.e. it can be of any arity, and it’ll get called first). That means that compose is just the reverse of pipe, which means we could just write pipe and then define compose as a call to pipe with the function list reversed. Lets.

Ok, so that seems like a good setup: we get both pipe and compose for the price of one. Now we just need to figure out what the “magic” is. That is, once we have all the pieces to execute the entire chain (all the functions to nest and all the arguments that set the chain in motion), how is it structured?

There are two major possibilities to explore here. We know that we have a list of things that we want to boil down into a single thing. We already discussed this: that sort of operation implies that we want to do a reduce/foldl-like operation: run through each item in turn and modify a single, final result (in this case, returning the fn(fn(fn(fn()))) nesting of functions as if we’d written it out that way, but without having to). That means that the complete pipe would look something like this:

Neat! Note again that by passing in just the first set of arguments, the functions, we’re not actually running the reduce operation yet: we get back a pre-configured function that will wrap and run everything… but only once the next chunk of arguments is passed in.

But reduce isn’t the only way to emulate that nesting logic. There’s another possibility worth thinking through: recursion. That is, instead of reducing the list of functions to simulate the wrapping, we could just have the pipe function keep calling itself until it’s run out of extras.

That is, in this recursive version of pipe, the first function passed in is always called with the final, starting arguments. If there are no more functions (i.e. …fns is empty and fns.length is 0), we just return that and we’re done. But if there are more functions, then we instead return the result of calling pipe with those remaining functions and then calling THAT with the result of fn(…args). Each time through, the (fn,…fns) construct splits out the first function for special treatment, and then only the remaining list of functions go on through for the next loop. Eventually, …fns will empty out, fns.length will be 0… and we’ll get our result.

Really cool. Is this better? Well, this recursion version looks like it might be tail-call optimized, though no current browser actually implements proper tail-call optimizations yet. Meanwhile, Array.reduce is probably already pretty darn optimized. And finally, it’s extremely unlikely that you’d ever use compose or pipe on a HUGE array of functions: functional programming generally just uses these constructs to glue together a handful of operations at a time, not the thousands of iterations it takes to make one start worrying about performance. So I probably wasted your time just to discuss something I thought was a cool little sidebar.

And actually, I probably did that twice.

Because, honestly, using some extra Array sequencing functions to handle arrays isn’t that much of a hardship or overly imperative. And if we’re willing to do it, we can just write compose directly like this, without bothering to create pipe:

Now, hey, pipe is a useful functional tool in its own right, & it was worth thinking through how to create it, but we didn’t need to make it. Sorry about that.

Actually, sorry again: I got so wrapped up in complaining about how rest parameters work that I forgot how compose works. It’s all f(g(h(x)))-ish, right? So while the h function there is certainly significant because it’s innermost, and should run first, the f function there is also really significant because it’s outermost. That is to say, we can write a recursive version of compose using rest parameters: we just have to rethink how the recursion works:

Now it does do exactly what we’d hope: it keeps recursively calling compose with the first function treated differently and popped off the stack each time, running until it’s simply returning the last (rightmost) function. That’s then called with initial set of arguments (it’s the last thing returned before the (…args)) and returns its result out to all the other onion layers wrapping it.

How this actually all works in practice when you’re composing together two functions is relatively obvious:

compose(f, g) → is…
f( compose(g)(…args) )compose(g) just returns g, so…
f( g(…args) ) → which is the same as f(g(x)))!

But when it’s three or more functions, it might be slightly harder to think through how the outer arguments end up getting called by the rightmost function:

compose(f, g, h) → is…
f(compose(g,h)(…args)) → which is…
f( (…args2 => g(compose(h)(…args2))) (…args))compose(h) = h
f( (…args2 => g( h(…args2))) (…args)) → same as f(g(h(x)))!

So, not as readably obvious how it all works, but the point is: it works, and now we can define compose all by itself without any helper functions or having to define, then reverse, pipe.

Nice.

Ack, sorry again!

As observant coder

has pointed out, maybe all these gyrations to try and treat the first function (i.e. the last, rightmost) in a special fashion are themselves a complete red-herring. Here’s his take on things:

See that? We’re building up a function, so the reducing function can just return a function each time. And since this is a composition (joining up functions), we can probably ignore the empty case (the oddball case of compose() when there are no functions to join: though if we wanted, we could just default to returning x=>x for that empty case). Since Array.reduce is comfortable with that (simply passing the first item as the accumulator), it works.

What I especially like about this formulation is that instead of naming the arguments to the reducing function the traditional (acc, x), it names them (f, g) which is exactly what we’re up to here: sticking a g inside our outer f

--

--

Drew Tipson

Primarily Javascript, potentially personal, possibly pointless. I welcome and am fascinated by your many marvelous opinions.