Underscore has become the standard functional JavaScript library in the eyes of many. It is mature, stable, and was created by Jeremy Ashkenas, the man behind the Backbone.js
and CoffeeScript
libraries. Underscore is actually a reimplementation of Ruby's Enumerable
module, which explains why CoffeeScript was also influenced by Ruby.
Similar to jQuery, Underscore doesn't modify native JavaScript objects and instead uses a symbol to define its own object: the underscore character "_
". So, using Underscore would work like this:
var x = _.map([1,2,3], Math.sqrt); // Underscore's map function console.log(x.toString());
We've already seen JavaScrip's native map()
method for the Array
object, which works like this:
var x = [1,2,3].map(Math.sqrt);
The difference is that, in Underscore, both the Array
object and the callback()
function are passed as parameters to the Underscore object's map()
method (_.map
), as opposed to passing only the callback to the array's native map()
method (Array.prototype.map
).
But there's way more than just map()
and other built-in functions to Underscore. It's full of super handy functions such as find()
, invoke()
, pluck()
, sortyBy()
, groupBy()
, and more.
var greetings = [{origin: 'spanish', value: 'hola'}, {origin: 'english', value: 'hello'}]; console.log(_.pluck(greetings, 'value') ); // Grabs an object's property. // Returns: ['hola', 'hello'] console.log(_.find(greetings, function(s) {return s.origin == 'spanish';})); // Looks for the first obj that passes the truth test // Returns: {origin: 'spanish', value: 'hola'} greetings = greetings.concat(_.object(['origin','value'], ['french','bonjour'])); console.log(greetings); // _.object creates an object literal from two merged arrays // Returns: [{origin: 'spanish', value: 'hola'}, //{origin: 'english', value: 'hello'}, //{origin: 'french', value: 'bonjour'}]
And it provides a way of chaining methods together:
var g = _.chain(greetings) .sortBy(function(x) {return x.value.length}) .pluck('origin') .map(function(x){return x.charAt(0).toUpperCase()+x.slice(1)}) .reduce(function(x, y){return x + ' ' + y}, '') .value(); // Applies the functions // Returns: 'Spanish English French' console.log(g);
The _.chain()
method returns a wrapped object that holds all the Underscore functions. The _.value
method is then used to extract the value of the wrapped object. Wrapped objects are also very useful for mixing Underscore with object-oriented programming.
Despite its ease of use and adaptation by the community, the underscore.js
library has been criticized for forcing you to write overly verbose code and for encouraging the wrong patterns. Underscore's structure may not be ideal or even function!
Until version 1.7.0, released shortly after Brian Lonsdorf's talk entitled Hey Underscore, you're doing it wrong!, landed on YouTube, Underscore explicitly prevented us from extending functions such as map()
, reduce()
, filter()
, and more.
_.prototype.map = function(obj, iterate, [context]) { if (Array.prototype.map && obj.map === Array.prototype.map) return obj.map(iterate, context); // ... };
You can watch the video of Brian Lonsdorf's talk at .
Map, in terms of category theory, is a homomorphic functor interface (more on this in , Category Theory). And we should be able to define map
as a functor for whatever we need it for. So that's not very functional of Underscore.
And because JavaScript doesn't have built-in immutable data, a functional library should be careful to not allow its helper functions to mutate the objects passed to it. A good example of this problem is shown below. The intention of the snippet is to return a new selected
list with one option set as the default. But what actually happens is that the selected
list is mutated in place.
function getSelectedOptions(id, value) { options = document.querySelectorAll('#' + id + ' option'); var newOptions = _.map(options, function(opt){ if (opt.text == value) { opt.selected = true; opt.text += ' (this is the default)'; } else { opt.selected = false; } return opt; }); return newOptions; } var optionsHelp = getSelectedOptions('timezones', 'Chicago');
We would have to insert the line opt = opt.cloneNode();
to the callback()
function to make a copy of each object within the list being passed to the function. Underscore's map()
function cheats to boost performance, but it is at the cost of functional feng shui. The native Array.prototype.map()
function wouldn't require this because it makes a copy, but it also doesn't work on nodelist
collections.
Underscore may be less than ideal for mathematically-correct, functional programming, but it was never intended to extend or transform JavaScript into a pure functional language. It defines itself as a JavaScript library that provides a whole mess of useful functional programming helpers. It may be a little more than a spurious collection of functional-like helpers, but it's no serious functional library either.
Is there a better library out there? Perhaps one that is based on mathematics?