Tuesday, March 10, 2015

Fixed argument counts with lodash ary()

When using functions as callbacks to lodash collection functions, we have to be careful to get the arguments right. This is because functions like map() actually pass more than just the current collection item as the first argument, which is all we need in the majority of cases. It also passes the current index, and the collection itself. Using callback functions that only make use of the first argument is easy. They just work. But if you want to use a function that takes more than just the current item as arguments, then weird things can happen.

The most common scenario for having callback functions that accept multiple arguments is when we compose functions using the lodash higher-order function tools. For example, let's say we have the following collection:

var collection = [
    [ 1, 2, 3 ],
    [ 1, 2, 3 ],
    [ 1, 2, 3 ]
];

Now, let's compose a callback function using partialRight(), and use it to map() our collection:

_.map(collection, _.partialRight(_.at, 0, 2));
// →
// [
//   [ 1, 2, 2, 2, 1, 3 ],
//   [ 2, 2, 2, 2, 1, 3 ],
//   [ 3, 2, 2, 2, 1, 3 ]
// ] 

Yikes, that's definitely not what we had in mind. The issue, is that our partial function is expecting three arguments — the first two are already applied. We just want the current collection item applied as the first argument. But instead, we get three unexpected arguments, hence the wacky output. Let's use the ary() function to fix the number of arguments provided to our callback:

_.map(collection, _.ary(_.partialRight(_.at, 0, 2), 1));
// →
// [
//   [ 1, 3 ],
//   [ 1, 3 ],
//   [ 1, 3 ]
// ]

That's more like it. By wrapping our callback function using ary(), we've specified that only one argument get's through to the callback function, in this case, it's the current collection item. The ary() function is relatively new. So the technique I used in Lo-Dash Essentials involves using compose() and identity() to achieve the same result:

_.map(collection,
 _.compose(_.partialRight(_.at, 0, 2), _.identity));

The identity() function just returns the first argument that's passed to it. So this is a valid technique for getting only the first argument. The ary() function makes this more explicit, and adds the ability to specify exact argument counts.