Книга: Functional Programming in JavaScript
Назад: Functional libraries for JavaScript
Дальше: Fantasy Land

Underscore.js

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 and libraries. Underscore is actually a reimplementation of Ruby's 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 method for the object, which works like this:

var x = [1,2,3].map(Math.sqrt);

The difference is that, in Underscore, both the object and the function are passed as parameters to the Underscore object's method (), as opposed to passing only the callback to the array's native method ().

But there's way more than just and other built-in functions to Underscore. It's full of super handy functions such as , , , , , 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);

Note

The method returns a wrapped object that holds all the Underscore functions. The 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 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 , , , and more.

_.prototype.map = function(obj, iterate, [context]) {   if (Array.prototype.map && obj.map === Array.prototype.map) return obj.map(iterate, context);   // ... };

Note

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 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 list with one option set as the default. But what actually happens is that the 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 to the function to make a copy of each object within the list being passed to the function. Underscore's function cheats to boost performance, but it is at the cost of functional feng shui. The native function wouldn't require this because it makes a copy, but it also doesn't work on 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?

Назад: Functional libraries for JavaScript
Дальше: Fantasy Land

bsn
thank
Vesa Karvonen
I hope you don't mind, but I’d like to point you and your readers to my high-performance optics library for JavaScript that is in production use in multiple projects, has comprehensive support for partial optics and interactive documentation: https://calmm-js.github.io/partial.lenses/ (this takes a moment to load — be patient!)