Get Functional with Underscore-Contrib

UnderscoreJS

TL;DR - Fogus and Jeremy Ashkenas published the underscore-contrib repo recently which is loaded with functional JavaScript goodness based on the popular underscore library. I walk through a few examples of how I use it in the hopes you’ll get interested in working out new ways to solve your own day-to-day issues.

Table of Contents!

Background

Functional programming has been getting a lot of play recently, but many demos of languages like Clojure and Haskell can be pretty intimidating and esoteric for your average front-end developer who primarily works in JavaScript. The reality is that JavaScript packs a lot of functional power and you can exploit this in your day-to-day work to get real results writing leaner and more flexible code.

If you don’t already use or know about Jeremy Ashkenas’ Underscore.js, then just stop right now and download it. Underscore provides a rich toolset of functions that ease manipulating Arrays, Objects and Functions. More importantly it brings higher order functions like map and reduce, native to modern browsers, to older versions of Internet Explorer. (In my examples I’m going to show how to do it both natively and with Underscore.)

Michael Fogus (Fogus) put together the Underscore-Contrib library, which brings a whole new set of functional goodness to JavaScript using underscore.js as the foundation. Reginald Braithwaite1 contributes to it as well and has written two of the better books about JavaScript: CoffeeScript Ristretto and JavaScript Allongé. Fogus is working on Functional JavaScript

This post is based on a quick talk I gave at an Arc90 Code Review. The main idea is that with underscore & underscore-contrib you can think about some problems in a functional rather than imperative way, and may arrive at more flexible and powerful solutions to a given problem. Additionally I dislike programming demos that use factorial and other math concepts to explain functional programming. My own contrived examples are pulled from the day to day stuff I (and maybe you) do at work such as String and Date manipulation.

How to get it

Clone the repo: $ git clone git@github.com:documentcloud / underscore-contrib.git
Or download a zip: master.zip

Demo code: functions.zip

JsFiddle: I put together a JSFiddle here which has all of the libraries loaded up and a log function to output your work in HTML. You can use this to play along with the examples below. (The log function itself uses underscore.js.)

Those promiscuous functions

Let’s say I’m working on a simple web interface for a client, and I need to do some string manipulation on data that I get from the server. I’ll need to do this repeatedly and in different ways, so it’s in my best interest to break my code out into little discrete functions that do one thing:

// Make the first character of a string Upper Case
var upperFirst = function (s) {
    return s[0].toUpperCase() + s.slice(1);
};

// Wraps native String.toLowerCase() - will show you why in a min
var lower = function (s) {
    return s.toLowerCase();
};

// Returns String prefixed by Mr.
var toMr = function (s) {
    return "Mr. " + s;
};

Hey cool, I have three functions for operating on Strings, that I can use in different ways to modify my data. JavaScript functions can be passed around any which way, so hey, let’s use them!

var ted = toMr(upperFirst(lower('TED')));

console.log(ted) // Mr. Ted

That works, sure, but what if I need to do the Mr. transform in a lot of different places? I don’t want to type out toMr(upperFirst(lower('n'))) every time do you? So you would probably make another function like:

var makeMr = function(s) {
	return toMr(upperFirst(lower(s))); 
}

console.log(makeMr('ted')) // Mr. Ted

Okay, much better, we’re assembling functions to get things done. But hey, what if we had a function that could automatically compose other functions together for us in a generic manner? We do.

// underscore: _.compose()
var makeMr = _.compose(toMr, upperFirst, lower);

console.log(makeMr('TED')) // Mr. Ted

// underscore-contrib: _.pipeline()
var makeMr = _.pipeline(lower, upperFirst, toMr);

console.log(makeMr('TED')) // Mr. Ted

compose and pipeline are two ways to get to the same place: a new function composed of other functions. As long as each function in the chain returns a value for the next function, as all of our string functions do, you’re set. The difference between compose and pipeline is the order in which your functions are called. compose calls them from right to left while pipeline is left to right.

Now that we’re quickly composing functions, we can build up our own library for this app:

// Return a capitalized String prefixed by Mr.
var makeMr = _.pipeline(lower, upperFirst, toMr);

// Reverse a String
// notice the use of _.explode() from underscore-contrib to 
// convert a String into an Array
var backwards = function (s) {
    return _.explode(s).reverse().join('');
};

// Make a function which reverses makeMr
var backMr = _.compose(backwards, makeMr);

console.log(makeMr('TED'), backMr('TED')) // Mr. Ted deT .rM

You’re making new functions on the fly now, using simple functions like Lego to make something more complex. This is pretty powerful when you think about it: all those admonitions to make small functions that do one thing well are starting to make sense.

Let’s do something a little more tasty: imagine you want to take an array of Strings and transform them, creating a new Object using the original string as the key and the transformed string as the value. You would probably be tempted to use a for-loop, or we could do something a bit different:

// GOAL: 
// get from ['ted','bob','jim'] to 
// {"ted":"Mr. Ted","bob":"Mr. Bob","jim":"Mr. Jim"}

// Start with a tuple from makeMr() like this: ['ted', 'Mr. Ted']?

// _.juxt() from underscore-contrib returns a function that 
// returns an array of the calls to each given function for some arguments. 
var j = _.juxt(_.identity, makeMr);

console.log(j('ted')) // ['ted','Mr. Ted']

// Hold up! What's _.identity?
// Identity function: f(x) = x - returns its argument unchanged
// Use it when you need a function as an argument but don't want
// to do anything to it.

// So, _.juxt(_.identity, makeMr) returns 
// [x, makeMr(x)] or ["ted","Mr. Ted"]

// Now let's take an Array of strings and make a new Array of tuples
var names = ['ted','bob','jim'];

// Native Array.map for modern browsers
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/map
var nameTuples = names.map(_.juxt(_.identity, makeMr)); 

// Underscore _.map if you need to be safe
var nameTuples = _.map(names, _.juxt(_.identity, makeMr)); 
// [["ted","Mr. Ted"],["bob","Mr. Bob"],["jim","Mr. Jim"]]

// Now make an Object from those tuples using _.object()
// which takes key value pair arrays [key, value] and 
// converts them into an Object {key : value}

console.log(_.object(nameTuples));
// {"ted":"Mr. Ted","bob":"Mr. Bob","jim":"Mr. Jim"}

// Now let's do it in a terse manner without all the local vars:
var nameObj = _.object(['ted','bob','jim'].map(_.juxt(_.identity, makeMr)));
// {"ted":"Mr. Ted","bob":"Mr. Bob","jim":"Mr. Jim"}

// Iterate!
var emanObj = _.object(['ted','bob','jim'].map(_.juxt(_.identity, backMr)));
// {"ted":"deT .rM","bob":"boB .rM","jim":"miJ .rM"}

var wtfObj = _.object(['ted','bob','jim'].map(_.juxt(upperFirst, backMr)));
// {"Ted":"deT .rM","Bob":"boB .rM","Jim":"miJ .rM"}   

See what we did there? By combining functions from underscore and underscore-contrib with our own functions we’re able to convert a simple Array of Strings into a new key value Object in one line of code. You can now quickly iterate through different function combinations to slightly or radically change the end Object as needed. By taking the time to reason through what you want to do you can compose a flexible solution.

There are a whole bunch of interesting function combinators to play with, way more than I can cover here.

Dates… effing dates…

So that was a pretty full-bore introduction into function composition and array mapping. Lets take that a little further by throwing reduce into the mix.

Another common task in JavaScript is working with Dates. I usually toss in a library like Moment.js to deal with this sort of thing, but let’s do something simple. Again, we’ll write some functions:

// Return a new Date based on String
var date_new = function (s) {
    return new Date(s);
};

// Utility function for to_iso8601
var date_pad = function (n) {
    if (n < 10) {
        return 0 + n;
    } else {
        return n;
    }
};

// Generate an ISO8601 formatted date from a Date object
// (!! there are better ways to do this)
var to_iso8601 = function (d) {
    return d.getUTCFullYear() + '-' + date_pad(d.getUTCMonth() + 1) + '-' + date_pad(d.getUTCDate()) + 'T' + date_pad(d.getUTCHours()) + ':' + date_pad(d.getUTCMinutes()) + ':' + date_pad(d.getUTCSeconds()) + 'Z';
};

// Return an ISO8601 String with the time removed 
var iso_no_time = function (s) {
    return s.split('T')[0];
};

Cool, we can make dates and convert them to ISO8601 strings. Let’s play:

console.log(date_new('5/5/2013')) // "Sun May 05 2013 00:00:00 GMT-0400 (EDT)"

console.log(date_new()) // Invalid Date

Whoops! If we forget to pass an argument to date_new() we get Invalid Date back. What if we don’t want that behavior for this application though? What if we want date_new() to have a default value? How many times have you written this kind of guard code:

var date_new = function (s) {
	if (s === null || s === undefined) {
		s = '1/1/1970';
	}
    return new Date(s);
};

console.log(date_new()); // "Thu Jan 01 1970 00:00:00 GMT-0500 (EST)"

You’ve probably written it a lot, and it can get tedious as times. Underscore-contrib gives us a nifty little function combinator called fnull that makes doing this type of thing easy:

var date_new = function (s) {
    return new Date(s);
};
date_new = _.fnull(date_new, '1/1/1970');

console.log(date_new()); // "Thu Jan 01 1970 00:00:00 GMT-0500 (EST)

fnull checks the argument for a null value, and if true passes in the default argument you defined (‘1/1/1970’). This is where you say: “Umm okay, but it doesn’t save me that much code, and it’s not inside my function so it may not be as clear.”

You’re right, but what if your function were more like this:

// Lots of arguments, none can be null
var create_datetime = function(year, month, day, hour, min, sec) { 
    var def = [1970, 1, 1, 0, 0, 0];
    if (arguments.length < def.length) {
        for (var i = arguments.length; i < def.length; i++) {
            arguments[i] = def[i];
        }
    }
    return new Date(
        arguments[0], 
        arguments[1], 
        arguments[2], 
        arguments[3], 
        arguments[4], 
        arguments[5]
    );
}

// Same thing using fnull
var create_datetime = function(year, month, day, hour, min, sec) {
	return new Date(year, month, day, hour, min, sec);
}

create_datetime = _.fnull(create_datetime, 1970, 1, 1, 0, 0, 0);

Not only is using fnull less verbose, but your defaults are not hardcoded inside create_datetime, so you can change them on the fly as needed in your application. Just another nice-to-have in contrib.

So now that we have some basic date functions, put them to work:

// Compose a function to make ISO8601 date strings
var make_iso = _.pipeline(date_new, to_iso8601);

// Our data
var dates = ['4/1/2012', '4/2/2012', null, '3/28/2012'];

// Turn these date strings into ISO8601 dates using map
var isos = dates.map(make_iso) 
// ["2012-4-1T4:0:0Z","2012-4-2T4:0:0Z","1970-1-1T5:0:0Z","2012-3-28T4:0:0Z"]

// Let's take those isos and break the time off
var isos_no_time = isos.map(iso_no_time);
// ["2012-4-1","2012-4-2","1970-1-1","2012-3-28"]

// Notice that the null in the array was caught by 
// fnull and defaulted to 1/1/1970

Now that we haves arrays full of dates, we may want to find the oldest & newest dates. Array.reduce is a great way to find those values. Array.reduce is an accumulator, so it walks down your array left-to-right operating on the values to return a single new value (the underscore equivalent of reduce is here). Let’s take a look:

// Let's find the most recent date in that array using reduce
// As the function walks down the array, it compares the current value
// to the next value on the right. If the current value (l) is 
// greater than the next (r) use l as the current value in the next
// comparison, otherwise use r.
var recent = isos.reduce(function (l, r) {
    return (iso_no_time(l) > iso_no_time(r)) ? l : r
});
console.log(recent); // 2012-4-2T4:0:0Z

// Let's find the oldest date in that array using reduce
// We do exactly the same as above, but we flip the comparison
// operator to l < r
var oldest = isos.reduce(function (l, r) {
    return (iso_no_time(l) < iso_no_time(r)) ? l : r
});
console.log(oldest); // 1970-1-1T5:0:0Z

So, just like with our strings, we’re able to quickly compose functions to operate on dates and iterate over collections with a very small amount of code. Additionally, your intent is made clear in the code itself: “I want to reduce this array of dates to the most recent date.”

Hopefully this jumble of code ignites your curiosity to go digging into these libraries. There are a lot of very interesting looking function combinators (that I am still figuring out how to use) which might inspire another article.

  1. Thanks for the clarification!

Comments