Why use those ugly compose functions when you can have this fancy dot instead?

Function composition is the process of applying a function to the output of another function.

If you are JavaScript developer, you are probably already familiar with it or at least have heard about it. Despite functional programming being popular in JavaScript land for quite a while now, there is still a noticeable lack of functional programming syntax in the language. If you want to compose two functions you are forced to write your own compose function or use the already available ones in libraries such as Lodash and Ramda… or are you? Let’s look at the following code:

“Is this even valid JavaScript?”, you might say. The short answer is “yes” and not only that. Lets look at the log:

What sorcery is this??

It works exactly as how you’d expect it to work in a language that has the composition operator. But JavaScript doesn’t… so how was it done? Let’s find out!

The member operator

There is a strange yet convenient property of the member operator in JavaScript: you can have as much white-space around it as you want. That means that

obj.a

is the same as

obj . a

Which, as you’d probably guessed already, means that it’s perfectly suited to be masked as a function composition operator. But how do we overload it?

2. Enter ES6’s Proxy

The Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).

The Proxy object is one of the less famous features of ES6 and that’s understandable —it’s slow, useless outside metaprogramming and not as flashy as the other ES6 built-in objects such as Promise and Map/Set.

However, it’s great fun for experimenting! Let’s get back to the example:

A proxy trap is basically method overloading. To accomplish the example above we need to implement 2 traps: the get and the apply one. The get trap handles property access and the apply trap handles function calls. First, let’s define the proxy generator function:

Our first task is to be able to generate any function on the fly. Luckily, the name parameter you see in the get trap provides us with the property that is being accessed. When the map or filter properties are being accessed, we’ll return the equivalent functions instead. The only finishing touch is to wrap the returned function in compose so that you can chain the methods infinitely.

(For the actual functions such as map and filter, I’m going to use Ramda since the default ones are not curried.)

The get trap is pretty much done, now we can focus on what happens when any of the functions is called. The apply trap has 3 parameters but we can safely ignore the that parameter and think of how we can use the other two. target is the function that is being called and args are… well, the arguments it was called with. In order to compose functions we need to keep track of which function was called and with which arguments so that we can run them all at the end. So we’re going to add a second argument to the compose function — one that will pass the previously composed function to the next one.

We are almost there! The only thing we’re missing is a stop sign because we don’t know when the composition stops and we want the real result instead of a proxy object. After some tinkering I narrowed it down to the following pattern: a function is called as the last composed function and then the composition is called with some arguments. So what we have here is two consecutive function calls. I know this is fairly limiting as you can’t compose functions without providing them with some arguments but the code in this post is more about exploring JavaScript than building something useful. With the previous findings in mind, we can track consecutive function calls with another argument to the compose function.

Here is the code in it’s whole glory 🌟 (or it’s whole shame 😅)

I published this snippet so that you can play around with it and it’s also on Github. Keep in mind it’s very incomplete and bug-prone, but pull requests are always appreciated!

const { map, filter, find, groupBy } = require('composable-ramda')

Thanks for reading, I hope you learned something new today!

Resources

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy