Higher-order functions are functions that can do one or both of the following:
Most programmers by now are familiar with higher-order functions such as map
, reduce
, and filter
. These functions abstract away the boilerplate when processing collections. For example…
Given a string-capitalizing function and an array of arrays of strings:
function capitalize(s) { return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase(); } var fruitsOfTheWorld = [ ["apple", "pineapple", "pear"], ["manzana", "pera", "piña"], ["poma", "perera", "ananàs"] ];
…we can turn this:
var results = [], i = fruitsOfTheWorld.length; while (i--) { results.unshift([]); var j = fruitsOfTheWorld[i].length; while (j--) { results[0].unshift(capitalize(fruitsOfTheWorld[i][j])); } } return results;
…into this:
return fruitsOfTheWorld.map(function(fruits) { return fruits.map(capitalize); });
In this post, I’ll demonstrate the usage of higher-order functions outside of a collection-processing context – with the ultimate goal of reducing boilerplate code in your real-world applications (as filter
did above). We’ll start with partial function application (facilitated by Underscore’s _.partial
) and move on to writing our own higher-order functions.
function sum(x, y) { return x + y; }
Given the code above, partial application refers to the application of the function sum
to between 0 and n-1 arguments, where n is equal to the number of parameters declared by the function. The result of partially applying a function to arguments is a new function with some (or none) of its parameters filled in with values:
var sumWithXFixedToTen = _.partial(sum, 10); (typeof sumWithXFixedToTen); // Function
The name sumWithXFixedToTen
is bound to a new function returned by _.partial
that behaves like sum
– but where x
will always be the value 10 (that is to say, sumWithXFixedToTen(y)
equals sum(10, y)
for all y
). Moving forward, this new function can be fully-applied with just a single argument (since sum
was already partially-applied to 10
).
sumWithXFixedToTen(20); // 30 sumWithXFixedToTen(99); // 109
To better understand what is going on behind-the-scenes of libraries like Underscore, we will now work through our own implementation of partial
:
function partial(fx) { var firstArgs = Array.prototype.slice.call(arguments, 1); return function fx2() { var secondArgs = Array.prototype.slice.call(arguments, 0); return fx.apply(null, firstArgs.concat(secondArgs)); }; }
Our higher-order partial
function:
fx
to partially applyfx
will be partially applied (firstArgs
)fx2
that, when called, returns the result of applying fx
to the both firstArgs
and secondArgs
This version of partial
provides a convenient way of applying a function to some number of arguments – but lacks some of the nice features found in similar implementations provided by Underscore’s partial
and bind
, such as preservation of execution context and parameter-skipping. For the rest of this article, I’ll be using Underscore’s functions. For the curious, take a look at their implementation to see what ours is missing.
When working with libraries like async, programmers often find themselves relying on function expressions to shim application code into the signatures required by waterfall
, series
, and the like. Partial application of async-agnostic functions to known arguments can eliminate the need for these shims, significantly reducing overall code volume.
For a concrete example, let’s take a look at some real-life client code I came across today:
function createAccount(email, password, done) { var id = uuid(); async.waterfall([ function(callback) { Services.Accounts.provision(email, password, callback); }, function(accountId, callback) { Services.Account.enable(email, accountId, callback); } ], function(err, auth) { done(err, id, auth); }); }
Using Underscore, we can eliminate the anonymous function expressions completely:
function createAccount(email, password, done) { var id = uuid(); async.waterfall([ _.partial(Services.Accounts.provision, email, password), _.partial(Services.Notification.confirm, email) ], _.partial(done, _, id, _)); }
Something to note about Underscore’s partial
function is that it can accept a placeholder _
which indicates that an argument should not be pre-filled – but rather provided at call time. In our example, the done
function has had only its id
parameter pre-filled, leaving err
and auth
to be provided once our series of asynchronous operations completes.
A pet-peeve of mine is seeing Node.js-style error handling sprinkled all over a codebase:
expressRouter.get("/api/ledgers/:id", function(req, res) { UserService.getLedgerById(req.params.id, function(err, ledger) { if (err) { res.status(404).json({"message": "Not Found"}); } else { res.status(200).json(ledger); } }); }); expressRouter.patch("/api/users/:id", function(req, res) { UserService.getUserById(req.params.id, function(err, user) { if (err) { res.status(404).json({"message": "Not Found"}); } else { UserService.updateUser(id, req.body, function(err, user) { if (err) { res.status(400).json({"message": "Invalid data provided"}); } else { res.status(200).json(user); } }); } }); }
Notice a pattern here? In the asynchronously-executed callbacks that we provide for both getUserById
and updateUser
, we check the value bound to the first parameter of each callback (err
) and respond with some error code and response if its value is truthy. This pattern of checking for and responding to errors can be abstracted into a higher-order function, leaving the application’s happy path free of crufty control logic.
function ensure(notOkCode, notOkMessage, response, okFx) { return function(err) { if (err) { response.status(notOkCode).json({"message": notOkMessage}); } else { okFx.apply(okFx, Array.prototype.slice.call(arguments, 1)); } }; } var ensureFound = _.partial(ensure, 404, "Not Found"), ensureValid = _.partial(ensure, 400, "Bad Request"); expressRouter.get("/api/ledgers/:id", function(req, res) { UserService.getLedgerById(req.params.id, ensureFound(res, function(ledger) { res.status(200).json(ledger); })); }); expressRouter.patch("/api/users/:id", function(req, res) { UserService.getUserById(req.params.id, ensureFound(res, function(user) { UserService.updateUser(user, req.body, ensureValid(res, function(updated) { res.status(200).json(updated); })); })); });
Higher-order functions will help you write JavaScript with less cruft. By familiarizing yourself with Underscore’s partial
function (or something you write yourself), you’ll have one more tool for factoring out common patterns into reusable, higher-order little chunks. Functional programming FTW!