JavaScript allows to combine small functions into more complex ones. Each input small function can be very simple to test and understand, while easy composition allows to naturally express the desired sequence of actions.
Take a look at Reginald Braithwaite's slides
JavaScript Combinators or the
presentation video.
Notice one curious detail: instead of using map(data, callback)
he switched
to map(callback, data)
. Why?
Let us code a simple example to understand why putting callback first leads to simpler and more elegant code. Start with an array of numbers and select only the even ones:
1 | var numbers = [1, 2, 3, 4, 5]; |
Right now we are using JavaScript's prototypical inheritance. Every array
inherits from Array.prototype
, in particular we use method filter
.
Because we want to make a reusable filter call, we would like to avoid relying
on the prototype. At first we can use the prototype's method directly.
1 | var filter = Array.prototype.filter; |
Nice, but filter.call
is an immediate call. It cannot be passed around, or composed
with other functions. So let us write separate filter
function
1 | var filter = function (data, cb) { |
Our function filter
takes data as first argument and callback to run as second,
following the approach taken by underscore.filter
and lodash.filter.
Functional combinators
There are two ways in JavaScript to combine functions into new ones: composition and argument binding. Let us start with composition.
Instead of writing isOdd
function from scratch, we can compose the existing isEven
function with a new not
adapter function:
1 | var isEven = function (x) { return x % 2 === 0; } |
This composition is very simple and elegant: not(isEven)
is a new function, and its meaning
is clear from naturally reading the expression.
Functional binding with left to right argument binding is included in EcmaScript5.
We can create new functions using .bind
syntax.
1 | function larger(limit, x) { |
Here is the key observation about binding arguments and function reuse.
Place the arguments LESS likely to change first (on the left).
Take larger
function above for example. We are likely to reuse this function
with single limit and multiple values of x
. Thus by placing limit first, we
are allowing easier future reuse
1 | var larger3 = larger.bind(null, 3); |
Reusing filter via binding
Let us go back to the function filter
and how we can reuse it by binding
arguments. What could be a common use case for binding? I see binding the
callback function!
1 | function filter(data, cb) { ... } |
This approach does not work because we put data
as the first argument,
and callback second. Ok, let us write another functional adapter and
bind arguments from the right. Except this approach runs into troubles,
because both underscore and lodash sandwich the callback between arguments
function filter(data, cb, context) ...
The next version of lodash 2.5.0 will have selective argument binding, which allows to bind just some arguments, leaving others free
1 | var _ = require('lodash'); |
I prefer not to rely on 3rd party libraries when the existing tools work nicely, if only I planned ahead. So I place the callback function first, and data second.
1 | function filter(cb, data) { ... } |
Again, the JavaScript code is less likely to be dynamic than the data, so place data last.
Making _ more functional
If placing callback first makes the function easier to reuse, can we "fix" the existing methods in underscore or lodash libraries? Yes, by providing an easy way to reorder first two arguments (usually data and callback), and by providing a shortcut to reorder and bind first argument. I wrote a small utility scoreunder with just 2 functions
1 | // simple argument flip |
There is a tiny utility flip2 with same purpose in lodash-contrib library.
Second, the scoreunder provides a combined "flip and bind first argument" function
1 | var under = require('scoreunder'); |
Utility partialFn
makes underscore / lodash a pleasure to use.
Final thoughts
Thanks to John-David Dalton for pointing out the new things coming to lodash's API.
There is an interesting presentation you can watch: Hey Underscore, you are doing it wrong! discussing why callback should be first argument.
Other functional libraries, like ramda and wu place callback first.
These two great books discuss JavaScript functional programming in detail, showing all sorts of interesting combinators and adapters. I highly recommend reading them in chunks, since each part needs time to digest.