Wednesday, April 20, 2016

Null-coalescing operators: when and when not to use them

Alright, this is something I've seen too many times in JavaScript code—even professionally—so I am going to address it here. This mistake is an easy one to make, and I use to make this mistake all the time; it's easy, simple, tempting, and nigh impossible to debug. I present to you: the null-coalescing operator. That is, the simple double-bar ||. Now, you might be wondering, "what's so bad about it?" Well, nothing. It's just bad programmers using it incorrectly.

The || operator is used in JavaScript not only to describe a disjunction, but also to specify default parameters to a function. (It's used in place of default parameter assignment, i.e. the one in function foo(bar=5){}, setting bar to 5.) It might be used (correctly) in the following situation:
function add(x, y, z){
 z = z || 0;
 return x + y + z;
}
This function successfully does what it's supposed to: add x and y, and z if specified. This works because of the short-circuiting property of ||. This operator is roughly equivalent to the following function:
function disjunction(LHS, RHS){
 if(LHS){
  return LHS;
 } else {
  return RHS;
 }
}
In this case, when the LHS is undefined, the value of the expression is RHS. However, the obverse of the statement (i.e. when LHS is defined then the value is not RHS and is thus is LHS) does not hold. Observe:
function multiply(x, y, z){
 z = z || 1;
 return x * y * z;
}
This works for most testcases (try it yourself) except for when z === 0; the expected result is 0, but what we get is x * y. Why? Well, let's look at what the disjunction yields. z = z || 1 becomes z = 0 || 1, which becomes z = 1, the incorrect result. This is why I follow this rule: when the RHS to the disjunction is truthy, use typeof z === "undefined" ? default : z; otherwise, one can use ||.

No comments:

Post a Comment