Nullish unary operator `?`

What?

In short, the idea is to have a ? operator to test whether a variable is nullish or not.

if (?a) {
  // do something if a is defined (i.e. not undefined or null)
}

Instead of having to write:

if (a !== undefined && a !== null) {
  // do something
}

Why?

The main reason is to continue to simplify and shorten nullish variable comparison.

Over the past years, more and more ways of handling nullish values were introduced in the language:

  • the optional chaining operator ?.
  • the nullish coalescing operator ??
  • the logical nullish assignment ??=

Those additions were very handy, but something is still missing: what if we simply want to test whether a variable is nullish or not?

We would still have to write something like:

if (a !== undefined && a !== null)
// OR using loose comparison
if (a != null)
// But using loose comparison is not a good practise, as it can be more confusing (and slower)
// (and moreover it does not handle the special `document.all` case!)

On the other hand, if we just want to see if a variable is falsy, we'd just have to write:

if (!a)

which is very short to type and to read.

The problem here is that the ! matches any falsy values, like "", NaN or even 0.
That would be great to have such a syntax, but to only check whether the variable is nullish or not.

How?

So:

if (?a) {
  // do something if a is defined
}

would simply be syntactic sugar for:

isDefined(variable) {
  return variable !== undefined && variable !== null;
}

if (isDefined(a)) {
  // do something if a is defined
  // note that the the variable is only evaluated once
}

Operator precedence

The operator precedence might be at the same level than the logical not operator (level 17), with left-to-right associativity.

Handling undeclared variables

I don't think that the ? operator should handle undeclared variables, like when using the typeof syntax: typeof a === "undefined".
The other nullish operators ?? and ?. throw a ReferenceError if the variable is not declared, so it would be coherent that the ? does the same.

Pros & Cons

I'd like to discuss several pros and cons about this proposal:

Pros

Readability

I personally find this syntax short, clean and understandable.
What's more, we remove the double negation of the evaluation:

a !== undefined

is read as: "a not undefined", whereas:

?a

is read directly as: "a defined"

We can also easily invert the condition, testing if the variable is not defined, like:

if (!?a)

In this case, the syntax still remains readable and easy to understand.

Backward compatibility

The proposed ? operator should not break any previous code.
While the ? operator is already used for ternary condition condition ? expressionTrue : expressionFalse, as it expects 3 members and a second : operator, there is not risk that it would be mistaken by Javascript engines / parsers.

Usage with optional chaining

This operator also goes really well alongside the optional chaining operator:

const obj = { sub: { value: 0 } };
if (?obj?.sub?.value) {
  // do something if obj.sub.value is defined
}

Cons

Possibly confusing syntax

The proposed syntax could be confusing, especially when using alongside ternary conditions.
For instance, the expression: ?a ? ?a : b ?? ?c is a real pain to read, while being totally valid.
But who is going to write such awful code like this in prod anyway? (:

Yet another way to check for undefined

There are already (too) many different ways to evaluate undefined variables, each one coming with their own pros and cons. Adding a new one might make this even more complicated and cluttered.

Please, give your thoughts :)

Do not hesitate to give your thoughts about this proposal, whether you think it might be useful or not for the language, discuss about pros and cons!

7 Likes

But using loose comparison is not a good practise,

Tools like eslint can be configured to only allow loose equality when comparing with null.

Are people checking document.all often enough to worry about it?

There is also one other way (not very nice) way to do it with current syntax.

if ((a ?? undefined) !== undefined) {
  ...
}
2 Likes

Indeed, when I was thinking about this proposal, I tried several ways to solve the use case by various means, including using the nullish coallescing operator, but as this was not really shorter nor cleaner, I didn't talked about it.

I know this is true, but I think this rule configuration exists precisely because we don't have a better solution for this for now! :)

(Also document.all was just a remark passing by, not an argument against using loose equality. document.all is deprecated and we should not use it anyway!)

Too make it clear, my goal with this proposal is not to add something because we don't have it yet™, but to come with a shorter and cleaner solution for a use case we encounter very often when programming in javascript.

We can have a look to the possible solutions in application with this example:

// Checking if something is defined: the explicit way
if (user.id !== undefined && user.id !== null && name !== undefined && name !== null) // HELP

// Only checking undefined
if (user.id !== undefined && name !== undefined) // solving half of the problem (and still very verbose)

// Using loose equality
if (user.id != null && name != null) // shorter indeed, but still verbose (and could be a bit cleaner)

// Using `?` proposal operator
if (?user.id && ?name) // shortest possible (/ cleanest possible too?)

The argument for conciseness probably won't get this idea far, == null is a commonly used exception to the "no loose equality" rules and is already pretty concise. I doubt an operator whose primary purpose is to shave off a few more characters would get very far.

The argument that adding this operator would make this language less confusing for new-comers is much stronger. It is easier to explain to newer people to never use loose equality, and to use an operator like this for null/undefined checking, as opposed to explaining to them to "Don't use loose equality, except when doing a null/undefined check - then it's okay to use == null - it doesn't look like it, but == null will check both null and undefined, and nothing else."

I don't know if this argument is strong enough to warrant additional syntax. But maybe it is strong enough to warrant an additional function, i.e. isDefined(someValue) - this won't provide a more concise syntax that @Clemdz lemdz wanted, but it does help with onboarding new developers into the language. With projects with lots of newer developers, it's easy enough to just ban loose equality and have them use this function to check for null/undefined. For projects with more seasoned developers, they could potentially choose to mostly ban loose equality, except for the == null case, which is slightly more concise. But more seasoned developers might still prefer isDefined() because it's less magical and more clear.

If we choose to go forwards with the ? operator - My preference is that it is a postfix operator, coffeescript style.

if (data.user.address?) { ... }

It reads better to me, but I guess that's a completely subjective point of view.

1 Like

A postfix ? operator would need some lookahead production rules so it can be distinguished from the beginning of a ternary operator.

e.g:

let r = a?

label: {
  prop
}

is already valid syntax that parses to:

let r = a ? (label) : ({ prop });
5 Likes

I'm constantly surprised at how many times the label: statement syntax gets in the way of so many syntax proposals on this forum.

3 Likes

Thanks for your feedback @theScottyJam!

I totally agree on what you said about loose equality confusion by new-commers.
Loose equality is one the most awkward feature of javascript, and if we could get rid of it definitively (if only!), I think that would be for the better.

Concerning using the operator as a postfix, I wasn't aware it was actually a thing in coffescript! :)

When I was thinking about the operator design, I was also hesitating between prefix and postfix, because
if (a?)
can be better read as "if a defined", compared to
if (?a)
("if defined a")

At the end, I still decided to go for the prefix version for the following reasons:

  1. it is used the same way than the ! operator, so the syntax is already intuitive:
// Logical NOT operator
if (!a) { // if falsy }

// Unary Nullish Operator
if (?a) { // if not nullish }

However, if we're comparing the two operators, it's good to note that ! will return true for falsy value, whereas ? will return true only for non-nullish values (0, "", NaN, ...). So the ? operator has nothing to do and should not be summarized with: "a ! operator but that only works with nullish values"

  1. It is quicker to see what's going on (might be subjective):

Having the operator at the end of the variable, you realize that you are evaluating whether the variable is defined (and not the variable itself) only at the end of the condition

if (data.user.address?) {} // you could first read it as: `if (data.user.address)` before seeing the `?` at the end

whereas when it's in prefix position, you directly understand that you are checking whether the variable is defined or not:

if (?data.user.address) {} // you know from the first character what's happening

So maybe that's a small detail, maybe you don't necessarily agree, but that's still something I wanted to care about.

  1. It still looks clean when using negation:
// prefix version
if (!?data.user.address) {}

// postfix version
if (!data.user.address?) {} // it's a bit trickier to read

// The negative postfix version works better in coffeescript
// as there is a `not` operator that makes it look cleaner
if (not data.user.address?) {}
// but this only work in coffeescript, so is this not really pertinent here

But at the end, as @aclaymore pointed out, the postfix version could potentially be parsed as a ternary operator in some special cases, which would break backward compatibility :(

The only way to have this as a postfix operator, like in Coffeescript, would be to use another syntax than ?, but then it would no really be like coffeescript anymore.

2 Likes

This proposal seems logical continuation of the work started by nullish coallescing operator and optional chaining to get rid of null-or-undefined checks and make behavior stricter. So I'd vote yes to this.

What's about prefix vs postfix. Prefix looks more natural to me. Also it worth mentioning that this operator's behavior differs from not-operator (!) when applied multiple times:

let a

?a // true
??a // false
???a // false
????a // false

!a // true
!!a // false
!!!a // true
!!!!a // false

And IMO it should be named as not-nullish to be properly pronounced. It will simplify verbal communication and prevent misunderstandings, current name is error-prone.

3 Likes

Indeed, the name "Nullish unary operator" might not be the best name for this operator (but as it was describing rather well what it was about, that's the one I went for).

"Not-nullish operator" is already better, as it is more accurate about what it really does.

If anyone has different naming ideas this operator, please feel free to suggest :)

@Clemdz - your argument of how awkward it is to combine this operator with the not operator has got me convinced. Prefix is better in javascript.

Just for the sake of correctness (even if I understood your idea), I think you probably meant:

let a;

?a // false
??a // true
???a // true
????a // true

as ?undefined is evaluated to false in the first place :)

1 Like

I've created a github repository for this proposal:

2 Likes

Also, I'm looking for a champion who would be interested to advance this proposal through the different stages of the TC39 process :slightly_smiling_face:

Why reinvent the wheel !?

It is better to use already existed syntax in other languages:

if(a?) {
  ...
}

Hi @redradist,

Please, could you give examples of the languages you are thinking about?

Also, could you explain a bit more why this would be better compared to the arguments I already gave on this message?

Thanks!

That makes it harder to write a parser, because when you're parsing character-by-character, it is ambiguous if you mean a ternary expression. JavaScript's nature as parseable in a stream is exploited to parse while downloading.

2 Likes

Using multiple ? in a row should be a syntax error. There is no reason a programmer would intend to do this. Removing it lowers the risk of programmer error and it keeps the door open to define different semantics for ??a.

1 Like

Indeed, we shouldn't be able to use multiple ? in a row without parentheses, like for the + and - unary operators. I realize I didn't specified this behavior on my proposal, I'm updating it right now! :)

I dunno - you can use other unary operators multiple times in a row, even though it sometimes doesn't make sense.

> !!'x'
true
> + +2
2
> - -2
2
> ~~2
2
> typeof typeof {}
'string'
> void void 2
undefined
> delete delete x
true

Though, you're right that there's never a reason to do it. Plus, ?? x just looks like you're using the existing ?? operator without a left operand.

I guess I could see an argument for always interpreting "??" As the nullish coalescing operator, and if it's missing its left operand, that's a syntax error, but "? ?" Would be legal, and I'm not sure what the language would do with "???" - probably the same thing it does when it sees "+++", which is a syntax error too (though "+ + +" is not).

uh, so I guess I actually agree with you @CrazyPython - I just had to think out loud here apparently.