In this post, I’m going to explore the array includes(..)
idiom in JS, an emerging popular pattern to test if a variable’s value is included in a set of values.
A programming idiom is a patterned way of using one or more language features together in a particular, recognizable way. The result is a familiar approach for solving typical programming tasks, which generally helps the code be more readable and more maintainable. The more common an idiom becomes, the more it receives “best practice” status; but this is purely by accepted convention, not by formal specification.
Let’s consider a variable called department
that can hold one of five values: "sales"
, "marketing"
, "support"
, "tech"
, or "admin"
. And let’s say there’s an important task to perform only for the sales, marketing, and admin departments.
The most fundamental way of implementing such a check is an if
statement with ||
-combined conditions:
if ( department == "sales" || department == "marketing" || department == "admin" ) { performImportantTask(department); }
I’d wager most readers are quite familiar with this approach. And familiarity is the strongest component of readability, so we’re likely to accept this style of code readily.
Note: I used the ==
operator on purpose because in this example, I know/stipulate tha department
only ever holds one of 5 non-empty string values, and I know the values I’m comparing against are also non-empty strings. As such, ==
is perfectly safe because ==
and ===
will do exactly the same thing, but I save one character.
So is this an idiom for solving our task? Yes, of sorts.
Of course, this approach does require repeating the department
variable (not to mention the ||
operator in between) for each part of the test condition. If the overall condition includes more than one variable, the names can be quite helpful. But if we’re mostly/only repeating because the syntax of our language doesn’t allow us not to repeat, it might be time to look for another idiom.
ES2016 added a method to arrays called includes(..)
, which returns a boolean true
or false
if the value being searched for is included in the array.
So we could do this:
if ( [ "sales", "marketing", "admin" ] .includes(department) ) { performImportantTask(department); }
This is an emergent idiom (meaning it’s starting to catch on more widely) for avoiding the repetition of checking a variable (like department
) against a set of values.
I’ll say that it’s become my favorite idiom for this task, and so I use it quite often now.
Since we’re already identifying the possible values as a “set”, you might wonder why wouldn’t just use a Set
data structure instead of a generic array:
if ( (new Set([ "sales", "marketing", "admin" ])) .has(department) ) { performImportantTask(department); }
has(..)
is a little more nicely named than includes(..)
. And because it’s a Set, the operation might be implemented in the JS engine slightly more efficiently than the includes(..)
method on an array. I say “might” because performance assertions based on assumed/intuited implementation details are a favorite pastime of JS developers, but they’re rarely as clear cut and reliable as the claims.
Consider that even if has(..)
is slightly more performant than includes(..)
, the constructor of the new Set(..)
call itself takes in an array, so it’s pretty reasonable to assume that creation of the Set
instance that way must strictly be a little slower than just defining a plain array.
Moreover, the new Set(..)
expression is a bit awkward inlined in the if
statement. We might instead pull it out as a separate variable:
var departments = new Set([ "sales", "marketing", "admin" ]); if (departments.has(department)) { performImportantTask(department); }
The departments
variable here is too generic, and makes the code sound unnecessarily redundant. We’d probably want to instead use a name like relevantDepartments
or something like that.
Of course, that means we’re going to have to figure out a good name for the separate (and temporary!) variable every single time we use this idiom; since it’s an idiom, we’re expecting to do it a lot in our code. That’s going to get pretty tiresome, I think. For this reason, I prefer not to pull out the inline set/list literal into its own variable, except on limited occassions.
But whether we’re using an array or a Set
, there is an unfortunate downside: the semantic order is sorta reversed from how we would normally describe the task in English. Translating this code to english text implies a description like, “Does a set of values have/include this specific value?” But I think most people would describe the task itself as, “Is this specific value included in a set of values?”
There’s lots of cases where programmers have to encode their English pseudo-code in a different order because of the design of the language. My favorite (irksome) example is negation of boolean operators like in
, instanceof
, etc:
if (!("path" in settings)) { settings.path = "/tmp"; }
We translate this back to English: “If it’s not the case that the string ‘path’ is in the settings object”, which just feels awkward. Wouldn’t this be nicer?
if ("path" !in settings) { settings.path = "/tmp"; }
I could only dream.
But just because we do this kind of reversal of logic translation a lot doesn’t mean we should propagate it if we have any other options; those other options are at least worth considering.
So we could imagine a hypothetical feature designed to look like this instead:
if ( department.isIn("sales","marketing","admin") ) { performImportantTask(department); }
Or we could get really clever:
if ( department.is("sales").or("marketing").or("admin") ) { performImportantTask(department); }
Of course, defining methods (with very generic names like is(..)
or or(..)
) on the prototypes of all values could be a bit problematic for backwards-compatibility; there’s a decent chance existing code has done this out in the wild somewhere, so the JS feature would collide and potentially break that code.
What if we dreamed it up as syntax instead?
if ( department |==| ["sales","marketing","admin"] ) { performImportantTask(department); }
I’ve conjured up an imaginary |==|
operator here, which we could call the “set comparison” or “is in” operator. This operator could, under the covers, invoke the includes(..)
method on the array, passing in the left-hand operand (department
).
That would be cool, but the chances of my blog post idea becoming a reality in JS are pretty low. The best you might be able to do is define your own custom Babel transform so that you could use an operator like I’m suggesting and have it rewrite that code to the array includes form.
As we mentioned earlier, we already have an in
operator in JS. So what if it could work like this:
if ( department in ["sales","marketing","admin"] ) { performImportantTask(department); }
That would be nice, but unfortunately the in
operator already looks for keys (or rather, properties) in an object/array. In the above array, the strings are values, not keys/properties.
You could do this:
if ( department in { sales:1, marketing:1, admin:1 } ) { performImportantTask(department); }
Here we’ve converted our array of strings into an object whose properties are the “values” we want to check against. This works, but it’s certainly a bit more confusing and slightly tricky/verbose in needing the :1
value assignments to each property to be a valid object literal.
Of course, if we’re really being clever, we could generate that object from an array, like this:
Object.fromEntries( [ "sales", "marketing", "admin" ].map(v => [v,1]) ); // { sales:1, marketing:1, admin:1 }
Now, you almost certainly don’t want to put that expression inline in your if
. But then we’re back to pulling it out into another temporary variable, which forces us down the path of coming up with a good name for that temporary variable with every usage of the idiom:
// instead of: // var relevantDepartments = { sales:1, marketing:1, admin:1 }; // we could do this: var relevantDepartments = Object.fromEntries( [ "sales", "marketing", "admin" ].map(v => [v,1]) ); if ( department in relevantDepartments ) { performImportantTask(department); }
That’s a little better, but it still is quite heavy compared to the all-inline [ .. ].includes(department)
expression idiom we presented earlier. Is it worth all this effort just to flip the semantic around from “set includes value” to “value in set”?
There are probably a dozen other ways you might encode this simple task in code. My goal wasn’t to cover every single possible variation. And I’m sure there’s nuances that might cause you to lean one way or another depending on the context.
The thing is: picking a general purpose “idiom” creates a consistency, that leads to familiarity, which ultimately leads to readability.
Idioms are “standards” we define, and by “we” I mean all of us, the greater developer ecosystem. We take the stuff language designers give us, and figure out how we’re going to arrange that in code so our solutions to tasks are understandable and maintainable.
You may have an approach you really like, and you may even have some fellow team members who agree.
But does that make it an “idiom”? In a sense, yes. But in another, arguably greater sense, not yet. The question of idiom’ness is one that can only be answered over time, and by a large body of developers and lots of code they’ve written.
One developer’s clever re-arrangement of syntax/APIs does not make something an idiom. Idioms are not invented, they emerge, they spread, they grow.
So what this whole post comes down to is, despite some of the downsides of array includes(..)
as an idiom for “is this variable one of these values”, I think it’s starting to prove itself as an emerging and growing idiom.
I think the array includes(..)
idiom in JS is the best option given the various tradeoffs, the one which has the best shot (short of TC39 reading this post and giving us dedicated syntax!) of being generally applicable in our code bases.
As this post is hosted by Tabnine, you might wonder if there’s any relevant connection to semantic autocomplete suggestions. It turns out there definitely is!
The way Tabnine comes up with its suggestions in your code editor is by comparing your code against what it has seen before (not only in your code base but broadly across all permissively licensed open source code they’ve discovered).
In other words, if you use idioms that are generic and common across a broader ecosystem of developers, there’s a much better chance that a tool like Tabnine will recognize it and be able to suggest a close or exact match, saving you time and making you more productive.
Idioms FTW!