Functional JavaScript Wizardry
Since EcmaScript 5, JavaScript has gotten nice collection functions
like Array.map
, Array.forEach
, Array.filter
and the
usual suspects. However, they can be a bit clunky to use
when dealing with objects. Look at the following piece of code:
I map over an array of strings, trim each string, then join the resulting array of trimmed strings. Except it’s a bit too verbose for my tastes. I would like to write (and read) this instead:
The straightforward way to do that is to define the anonymous wrapper
function that calls trim
beforehand:
Okay. Except that I don’t want to call just trim
, I might call any
function on Array
, or Object
, or any custom created object for
that matter. And I certainly don’t want to write stupid wrapper
functions each time.
The trim
function already has a name: String.prototype.trim
. But
unfortunately, I cannot just write the following:
Because here map
will call trim
on each string in the array, but
without any context object (a value for this
). Instead, I would
like map
to pass each string as the context object to trim
, in
turn. We could redefine a custom version of map
that works that
way, but there’s a simpler solution.
Abstracting wrappers
What we really need is a function that transforms method calls into
function calls. We need a function m2f
to transform o.m(args)
calls into f(o, args)
calls, where f = m2f(o.m)
. Easy enough
in JavaScript (bar the ugliness of dealing with the arguments special
object):
Good! Now I can use m2f
on any “method”, and get back a more
flexible function. For instance, I can use map
on any “array-like”
objects like strings, the special arguments
object, NodeList
and
so on:
And of course, I can use m2f
on any method, not just map
. Here I
populate an array
object with functions from Array.prototype
that
work on any array-like object:
This replicates the Mozilla-specific “Array generics” extension. This extension is not available in V8-powered environment like nodejs.
This m2f
function is quite handy when you are used to a functional
style of programming. You can find it in Fogus’
Functional JavaScript under the name ‘invoker’. However,
there is another way to define m2f
using a powerful corner of
EcmaScript 5.1, bind
.
Calling and binding functions
Earlier, I said I could not just write:
Because JavaScript methods are really just functions. Now my trim
is pretty useless on its own, because it expects a context object.
There’s a way to provide one to it, using call
:
The call
function takes a context, some arguments, and calls the
receiver (a function) with them.
Another way to provide the context is to use the bind
function. As
its name implies, bind
will return a function with the supplied
context bound to it. Let’s see how it works:
Whenever trimA
is called, it will call trim
with "a"
as context,
even if trimA
is supplied another context with call
.
Binding call
Where does that lead us? Well, if we look at the precedent example again,
we see that trim.call
is essentially the function we want. But we’d
rather use trim
directly, without calling call
on it. How to
achieve that?
Notice that call
is itself a method call. So we can extract it on
its own, and use it to call trim
. But since it’s an extracted
method, we still need to use call
on it,
Since we are calling call
on itself, the notation is a bit
overloaded, but it works. Note that now, trim
is just an argument;
we can put any other method in there,
In fact, there’s nothing tying call
to trim
. We could have
defined call
without trim
, by taking it on Function.prototype
directly,
Nevertheless, the greatest benefit of this transformation is that we
can now use bind
to fix the first argument of call
to a specific
function.
And we just redefined m2f
!
Except it doesn’t work … because we did not provide a context to
bind
. Putting it all together,
Alternatively, since bind
is not tied to call
, it might be clearer
to just write:
Which finally gives this one-liner gem:
A very useful tool for writing terse JavaScript.
A variant of this form was given by Dave Herman and explained by Erik Kronberg, which I read thrice without really understanding what was going on. This article is my attempt to derive this magic one-liner in a way that makes sense for me.