The secret life of [context] in underscore and lodash

If you spend any time using either lodash or underscore as a utility library, you probably use a few functions that have a mysterious optional final parameter in the documentation. In underscore it is defined as [context] and in lodash [thisarg] (I will be illustrating the concepts with underscore from here on, but everything mentioned also applies in lodash). You may see certain references to what this context parameter does, and some simple examples on StackOverflow on how it could be used, but for me, what I saw never really helped much. I hope this will help you understand where it can be used and why it’s super useful.

As an example, we could look at the documentation for _.every. You could write a function to determine if all elements are equal to 1.

_.every( [1,1,1,2], function(i){
return _.isEqual(1,i)
}) // false

And we could clean that up a bit with _.partial

_.every([1,1,1,2], _.partial(_.isEqual, 1)) // false
Side Note: Any time we see that pattern where we are taking an argument into a function and there is a simple evaluation of another function inside (like _.isEqual) this is a great spot to use _.partial to construct the intended function without another anonymous function declaration; and you get rid of having to having to specify the return.

The previous example’s use of _.every is the basic case. Optionally, it takes a third parameter of [context]. To get a deeper understand, we need a better example.

Comparison of JavaScript Objects

So let’s say we have two array of objects that we want to compare.

var array1 = [
{ name: ‘Jane’, age: 58},
{ name: ‘John’, age: 25}
]
var array2 = [
{ name: ‘John’, age: 25},
{ name: ‘Jane’, age: 58}
]

We can see that they are the same, but in a different order. It seems like we should easily be able to use underscore to give us equality. Let’s try it out.

(_.isEqual(array1, array2)) // false

Darn. Not what we expect. It seems like underscore isn’t going to do a deep compare, and Array equality is not as robust as we’d like in JavaScript. This means we have to be a bit more creative.

We could do something like sort by a specific key, but this may fall down if our data objects get more keys.

_.isEqual(
_.sortBy(array1, ‘name’),
_.sortBy(array2, ‘name’)
) //true

For this example assume this won’t work because (of some crazy business requirements) what we really want is to determine if the list in question is a subset of the other list.

Let’s see if we can use _.some to check if one object is included in another collection.

obj = { name: ‘John’, age: 25 }
_.some(array1, _.partial(_.isEqual, obj)) // true

Can you think of a way to do this against two separate lists? There are a couple options that might come to mind.

_.every([array1, array2], function(x){/* split and compare here */}

That seems a bit contrived, and we can still only iterate over values in a single list. How about this?

_.every( [array1], function(x){
return _.some(array2, function(x){/* logic here */})
}

But if we do something like that we are no longer referentially transparent, because we refer to an outside (array2) variable from within our closure.

Also, by referencing array2, we bind any use of this function to that specific comparator array in the future. so if we want a comparator for another array, we need to expressly construct a new function that is identical to the above, with a different array reference. What we really want is a nice way to pass in the other array, and to be able to configure a function via composition for specific use cases.

The magic of [context]

The simple definition of context is the binding of this that will be used by the iterator. That seems super abstract so lets explore what that means in practice. In our last attempt we thought we might try something like:

return _.some(array2,...)

Well, if array2 was the context, then we’d be set because we could say:

return _.some(this,...)

So, lets see how that unfolds.

function iterator(item){
return _.some(this, function(x){
return _.isEqual(item, x)
})
}

We can, again, clean that up with _.partial

function iterator(item){
return _.some(this, _.partial(_.isEqual, item))
}
YMMV: I’m not going to claim this iterator function fits every edge case (use set operations in real life). We just need something simple enough to work through.

And then the application of that with _.every.

_.every(array1, iterator, array2) // true

Next Steps

At this point we have a pretty awesome comparator, but we might be able to take it a little further with our function composition. We could create custom functions, configured for a specific array case.

array1IsASubsetOf = _.partial(_.every, array1, iterator)

And then we can use it like so.

array1IsASubsetOf(array2) // true

And this is great, because now we can use that function as an argument to other underscore methods to, for example, filter a whole list of lists for arrays that are subsets of our array1.

_.filter(arrayList, array1IsASubsetOf)

No Loops (kinda)

When you take a look at the final code, I hope you find it devoid of most of the things we normally expect to see when comparing two lists of data. For one, there’s no loops (at least ones that we define), for another, there is no global pollution of intermediate values. Programming with functions, and dynamic scoping can be challenging, but really fun once you start to crack the surface.

Here is the entirety of the code for completeness. And a jsfiddle to play with.

var array1 = [
{ name: ‘Jane’, age: 58},
{ name: ‘John’, age: 25}
]
var array2 = [
{ name: ‘John’, age: 25},
{ name: ‘Jane’, age: 58}
]
var array3 = [
{ name: 'John', age: 25},
{ name: 'Jane', age: 58},
{ name: 'Josh', age: 12}
]
var array4 = [
{ name: 'Philip', age: 25},
{ name: 'Jane', age: 58}
]
function iterator(item){
return _.some(this, _.partial(_.isEqual, item))
}
subsetsOfArray1 = _.partial(_.every, array1, iterator)
_.filter([array1, array2, array3, array4], subsetsOfArray1)

Thanks for reading!

Special Thanks to Sean Griffin, Billy Griffin, and Bree Thomas for technical review.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.