{ "feed": { "id": "urn:uuid:2e8662fc-461f-58a7-9159-c22ca4880eb0", "link": [ { "@attributes": { "href": "http://darrennewton.com/atom.json", "rel": "self" } }, { "@attributes": { "href": "http://darrennewton.com/" } } ], "title" : "Miscellanea", "subtitle" : "Code, design & cultural ephemera from Darren Newton's brain", "updated" : "2016-12-21T15:18:26-05:00", "author": { "name": "Darren Newton" }, "rights" : "Copyright (c) 2016, Darren Newton", "entry": { "id" : "urn:uuid:d7124fe7-0b08-5a50-9220-8d7dcfb6bd35", "link" : "http://darrennewton.com/2013/05/05/get-functional-with-underscore-contrib/", "summary" : "
\nTL;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\u2019ll get interested in working out new ways to solve your own day-to-day issues. ", "content" : "
\nTL;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\u2019ll get interested in working out new ways to solve your own day-to-day issues.
\n\nFunctional 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.
\n\nIf you don\u2019t already use or know about Jeremy Ashkenas\u2019 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\u2019m going to show how to do it both natively and with Underscore.)
\n\nMichael 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\u00e9. Fogus is working on Functional JavaScript
\n\nThis 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.
\n\nClone the repo: $ git clone git@github.com:documentcloud / underscore-contrib.git
\nOr download a zip: master.zip
Demo code: functions.zip
\n\nJsFiddle: 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.)
Let\u2019s say I\u2019m 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\u2019ll need to do this repeatedly and in different ways, so it\u2019s in my best interest to break my code out into little discrete functions that do one thing:
\n\n:::javascript\n// Make the first character of a string Upper Case\nvar upperFirst = function (s) {\n return s[0].toUpperCase() + s.slice(1);\n};\n\n// Wraps native String.toLowerCase() - will show you why in a min\nvar lower = function (s) {\n return s.toLowerCase();\n};\n\n// Returns String prefixed by Mr.\nvar toMr = function (s) {\n return \"Mr. \" + s;\n};\n
\n\nHey 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\u2019s use them!
\n\n:::javascript\nvar ted = toMr(upperFirst(lower('TED')));\n\nconsole.log(ted) // Mr. Ted\n
\n\nThat works, sure, but what if I need to do the Mr. transform in a lot of different places? I don\u2019t want to type out toMr(upperFirst(lower('n')))
every time do you? So you would probably make another function like:
:::javascript\nvar makeMr = function(s) {\n\treturn toMr(upperFirst(lower(s))); \n}\n\nconsole.log(makeMr('ted')) // Mr. Ted\n
\n\nOkay, much better, we\u2019re 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.
\n\n:::javascript\n// underscore: _.compose()\nvar makeMr = _.compose(toMr, upperFirst, lower);\n\nconsole.log(makeMr('TED')) // Mr. Ted\n\n// underscore-contrib: _.pipeline()\nvar makeMr = _.pipeline(lower, upperFirst, toMr);\n\nconsole.log(makeMr('TED')) // Mr. Ted\n
\n\ncompose
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\u2019re 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\u2019re quickly composing functions, we can build up our own library for this app:
\n\n:::javascript\n// Return a capitalized String prefixed by Mr.\nvar makeMr = _.pipeline(lower, upperFirst, toMr);\n\n// Reverse a String\n// notice the use of _.explode() from underscore-contrib to \n// convert a String into an Array\nvar backwards = function (s) {\n return _.explode(s).reverse().join('');\n};\n\n// Make a function which reverses makeMr\nvar backMr = _.compose(backwards, makeMr);\n\nconsole.log(makeMr('TED'), backMr('TED')) // Mr. Ted deT .rM\n
\n\nYou\u2019re 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.
\n\nLet\u2019s 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:
\n\n:::javascript\n// GOAL: \n// get from ['ted','bob','jim'] to \n// {\"ted\":\"Mr. Ted\",\"bob\":\"Mr. Bob\",\"jim\":\"Mr. Jim\"}\n\n// Start with a tuple from makeMr() like this: ['ted', 'Mr. Ted']?\n\n// _.juxt() from underscore-contrib returns a function that \n// returns an array of the calls to each given function for some arguments. \nvar j = _.juxt(_.identity, makeMr);\n\nconsole.log(j('ted')) // ['ted','Mr. Ted']\n\n// Hold up! What's _.identity?\n// Identity function: f(x) = x - returns its argument unchanged\n// Use it when you need a function as an argument but don't want\n// to do anything to it.\n\n// So, _.juxt(_.identity, makeMr) returns \n// [x, makeMr(x)] or [\"ted\",\"Mr. Ted\"]\n\n// Now let's take an Array of strings and make a new Array of tuples\nvar names = ['ted','bob','jim'];\n\n// Native Array.map for modern browsers\n// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/map\nvar nameTuples = names.map(_.juxt(_.identity, makeMr)); \n\n// Underscore _.map if you need to be safe\nvar nameTuples = _.map(names, _.juxt(_.identity, makeMr)); \n// [[\"ted\",\"Mr. Ted\"],[\"bob\",\"Mr. Bob\"],[\"jim\",\"Mr. Jim\"]]\n\n// Now make an Object from those tuples using _.object()\n// which takes key value pair arrays [key, value] and \n// converts them into an Object {key : value}\n\nconsole.log(_.object(nameTuples));\n// {\"ted\":\"Mr. Ted\",\"bob\":\"Mr. Bob\",\"jim\":\"Mr. Jim\"}\n\n// Now let's do it in a terse manner without all the local vars:\nvar nameObj = _.object(['ted','bob','jim'].map(_.juxt(_.identity, makeMr)));\n// {\"ted\":\"Mr. Ted\",\"bob\":\"Mr. Bob\",\"jim\":\"Mr. Jim\"}\n\n// Iterate!\nvar emanObj = _.object(['ted','bob','jim'].map(_.juxt(_.identity, backMr)));\n// {\"ted\":\"deT .rM\",\"bob\":\"boB .rM\",\"jim\":\"miJ .rM\"}\n\nvar wtfObj = _.object(['ted','bob','jim'].map(_.juxt(upperFirst, backMr)));\n// {\"Ted\":\"deT .rM\",\"Bob\":\"boB .rM\",\"Jim\":\"miJ .rM\"} \n
\n\nSee what we did there? By combining functions from underscore and underscore-contrib with our own functions we\u2019re 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.
\n\nThere are a whole bunch of interesting function combinators to play with, way more than I can cover here.
\n\nSo 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.
\n\nAnother 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\u2019s do something simple. Again, we\u2019ll write some functions:
\n\n:::javascript\n// Return a new Date based on String\nvar date_new = function (s) {\n return new Date(s);\n};\n\n// Utility function for to_iso8601\nvar date_pad = function (n) {\n if (n < 10) {\n return 0 + n;\n } else {\n return n;\n }\n};\n\n// Generate an ISO8601 formatted date from a Date object\n// (!! there are better ways to do this)\nvar to_iso8601 = function (d) {\n 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';\n};\n\n// Return an ISO8601 String with the time removed \nvar iso_no_time = function (s) {\n return s.split('T')[0];\n};\n
\n\nCool, we can make dates and convert them to ISO8601 strings. Let\u2019s play:
\n\n:::javascript\nconsole.log(date_new('5/5/2013')) // \"Sun May 05 2013 00:00:00 GMT-0400 (EDT)\"\n\nconsole.log(date_new()) // Invalid Date\n
\n\nWhoops! If we forget to pass an argument to date_new() we get Invalid Date
back. What if we don\u2019t 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:
:::javascript\nvar date_new = function (s) {\n\tif (s === null || s === undefined) {\n\t\ts = '1/1/1970';\n\t}\n return new Date(s);\n};\n\nconsole.log(date_new()); // \"Thu Jan 01 1970 00:00:00 GMT-0500 (EST)\"\n
\n\nYou\u2019ve 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:
:::javascript\nvar date_new = function (s) {\n return new Date(s);\n};\ndate_new = _.fnull(date_new, '1/1/1970');\n\nconsole.log(date_new()); // \"Thu Jan 01 1970 00:00:00 GMT-0500 (EST)\n
\n\nfnull
checks the argument for a null value, and if true passes in the default argument you defined (\u20181/1/1970\u2019). This is where you say: \u201cUmm okay, but it doesn\u2019t save me that much code, and it\u2019s not inside my function so it may not be as clear.\u201d
You\u2019re right, but what if your function were more like this:
\n\n:::javascript\n// Lots of arguments, none can be null\nvar create_datetime = function(year, month, day, hour, min, sec) { \n var def = [1970, 1, 1, 0, 0, 0];\n if (arguments.length < def.length) {\n for (var i = arguments.length; i < def.length; i++) {\n arguments[i] = def[i];\n }\n }\n return new Date(\n arguments[0], \n arguments[1], \n arguments[2], \n arguments[3], \n arguments[4], \n arguments[5]\n );\n}\n\n// Same thing using fnull\nvar create_datetime = function(year, month, day, hour, min, sec) {\n\treturn new Date(year, month, day, hour, min, sec);\n}\n\ncreate_datetime = _.fnull(create_datetime, 1970, 1, 1, 0, 0, 0);\n
\n\nNot 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:
\n\n:::javascript\n// Compose a function to make ISO8601 date strings\nvar make_iso = _.pipeline(date_new, to_iso8601);\n\n// Our data\nvar dates = ['4/1/2012', '4/2/2012', null, '3/28/2012'];\n\n// Turn these date strings into ISO8601 dates using map\nvar isos = dates.map(make_iso) \n// [\"2012-4-1T4:0:0Z\",\"2012-4-2T4:0:0Z\",\"1970-1-1T5:0:0Z\",\"2012-3-28T4:0:0Z\"]\n\n// Let's take those isos and break the time off\nvar isos_no_time = isos.map(iso_no_time);\n// [\"2012-4-1\",\"2012-4-2\",\"1970-1-1\",\"2012-3-28\"]\n\n// Notice that the null in the array was caught by \n// fnull and defaulted to 1/1/1970\n
\n\nNow 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\u2019s take a look:
:::javascript\n// Let's find the most recent date in that array using reduce\n// As the function walks down the array, it compares the current value\n// to the next value on the right. If the current value (l) is \n// greater than the next (r) use l as the current value in the next\n// comparison, otherwise use r.\nvar recent = isos.reduce(function (l, r) {\n return (iso_no_time(l) > iso_no_time(r)) ? l : r\n});\nconsole.log(recent); // 2012-4-2T4:0:0Z\n\n// Let's find the oldest date in that array using reduce\n// We do exactly the same as above, but we flip the comparison\n// operator to l < r\nvar oldest = isos.reduce(function (l, r) {\n return (iso_no_time(l) < iso_no_time(r)) ? l : r\n});\nconsole.log(oldest); // 1970-1-1T5:0:0Z\n
\n\nSo, just like with our strings, we\u2019re 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: \u201cI want to reduce this array of dates to the most recent date.\u201d
\n\nHopefully 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.
\n\nThanks for the clarification! ↩
\n