New: Logical Assignment Operators
Since I finished the book, a new proposal has reached Stage 3 4 (as of the July 2020 meeting): Logical Assignment Operators. It adds three new compound assignment operators to the language: ||=, &&=, and ??=. At first glance it seems like these would be just like the existing compound assignment operators (+=, -=, and such), but they have a twist: They short-circuit (even the assignment part).
First let's look at basic operation, then we'll come back to short-circuiting.
Basic Operation
Like their mathematical cousins, the new operators combine an operation with an assignment. ||= (the proposal calls it "Or Or Equals") combines || with assigning back to the left-hand side:
let left = false;
let right = true;
left ||= right;
console.log(left); // true
Similarly, "And And Equals" does the same for &&:
let left = true;
let right = false;
left &&= right
console.log(left); // false
And "QQ Equals" (as the proposal calls it; I prefer "Nullish Equals") does it for nullish coalescing (see Chapter 17 of the book):
let left = undefined;
let right = 42;
left ??= right;
console.log(left); // 42
So far, so simple. Now the twist: Depending on the value of the left-hand side, the assignment part of that may not happen at all thanks to short-circuiting.
Short Circuiting
You may know that the logical operators short-circuit their operation. For instance, left || right doesn't evaluate right at all if left is already truthy; there's no need to evaluate the right-hand side, the operation will result in left's value. If the right-hand operand involved side-effects (perhaps it's a function call), the fact it isn't evaluated is important.
The logical assignment operators short-circuit too, but they don't just skip evaluating right's value, they also skip the assignment back to left! After all, there's no point in writing left's own value back to it. You can see that in practice if you're using an accessor property:
const obj = {
_left: true,
get left() {
const result = this._left;
console.log(`Getting left: ${result}`);
return result;
},
set left(value) {
console.log(`Setting left: ${value}`);
this._left = value;
}
}
// Using a simple assignment and `||`:
obj.left = obj.left || false;
// =>
// Getting left: true
// Setting left: true
// Using compound assignment:
obj.left ||= false;
// =>
// Getting left: true
Notice there's no "Setting" log line; that's because the setter wasn't called, because the operation short-circuited.
Details in the proposal of course, but here's the fundamental logic (slightly simplified) for left op= right where op is ||, &&, or ??:
- Get the Reference for
leftand call it lref. (A Reference is basically a spec mechanism for identifying where a value came from, such as a variable, parameter, property, etc.) - Get the value of lref and remember it as lvalue
- Perform the operation that
opwould perform on lvalue to determine whether to short-circuit; if short-circuiting, return lvalue and skip the remaining steps - Get the Reference for
right, get its value, and remember the value as rvalue - Store rvalue in lref
- Return rvalue
For ||= Step 3 is:
- Convert lvalue to boolean; if the result is
true, return lvalue and skip the remaining steps
For &&= it's the same but with false:
- Convert lvalue to boolean; if the result is
false, return lvalue and skip the remaining steps
For ??= it's just a straight check:
- If lvalue is neither
undefinednornull, return lvalue and skip the remaining steps
There were arguments on both sides of whether the assignment part should be short-circuited. Concerns about short-circuiting came from at least two perspectives:
- It makes these operators different from the mathematical compound assignment operators, which never short-circuit;
left += 0always writes back toleft, which is something you can observe if it's an accessor property. - It makes the naïve explanation "
left ||= rightis basicallyleft = left || right" incorrect, sinceleft = left || rightwill always write back toleftbutleft ||= rightmay not; which, again, is observable (for instance, ifleftis an accessor).
Countering those concerns:
- The
||,&&, and??operators are already different from mathematical operators.||,&&, and??short-circuit; mathematical operators don't. Since they're already different, it's reasonable that the compound assignment versions of them are different as well. - The naïve explanation is already incorrect.
left += rightis observably different fromleft = left + righteven though it always writes back toleft: In the compound form,leftis only evaluated once, but in the simple form,leftis evaluated twice. That's observable ifleftisn't just a simple variable or property reference, but something with a side-effect, likecounter[index++] += 42, which is quite a bit different fromcounter[index++] = counter[index++] + 42. - Ruby, C#, and CoffeeScript all have these operators, and all short-circuit them. Ruby's have had this behavior for well over a decade. Making JavaScript different in this regard would break people's intuition coming from other languages with similar operators.
Destructuring...?
You may be wondering, as I was, whether this is valid:
// Is this valid...?
({ left } ??= obj); // Meaning something like `left = left ?? obj.left`
No, it isn't. Which is consistent with how the mathematical compound operators are handled. For now, the assignment target must be simple, not destructuring.
I don't know why I never thought to wonder about that with mathematical compound assignments, but I never did; and yet, I immediately wondered about it with these logical ones, particularly ??=. That said, the cognitive load of understanding that destructuring nullish coalescing logical assignment is...well...pretty high. :-)
What I didn't think to ask, though, was a question asked by others in a couple of proposal issues, such as #7: Can you use ??= to provide a default value for a destructuring target when the source value is null (rather than only when it's undefined), like this?
// Can you do this...?
const obj = { value: null };
const {value ??= "default"} = obj;
console.log(value); // "default"
The answer is no, you can't, but not because people necessarily thought it was a bad idea; it's just that destructuring patterns are a completely different beast from compound assignment operators, so it was out of scope for this proposal. It could be the subject of a future proposal, though...
Wrapup
I'm really glad to see these new operators, and glad the semantics ended up the way they did. I'll definitely be using them.
Happy coding!
Have a question or comment about this post? Ping me on Mastodon at @tjcrowdertech@hachyderm.io!
