Optional Chaining (aka Existential Operator, Null Propagation)

# Claude Pache (8 years ago)

I have prepared a strawman for the ?. operator:

claudepache/es-optional-chaining, claudepache/es-optional-chaining

If there is interest in that proposal, I'm looking for a champion from TC39.

,

# John Lenz (8 years ago)

Can you reference something as to why the more obvious operators are problematic?

?. ?[] ?() ?:

# Waldemar Horwat (8 years ago)

On 02/03/2016 11:56, John Lenz wrote:

Can you reference something as to why the more obvious operators are problematic?

?. ?[] ?() ?:

Some of these have problems. For example, a?[]:b is already valid ECMAScript syntax and does something else. There is an analogous but subtler problem for a?(.

 Waldemar
# Claude Pache (8 years ago)

Le 3 févr. 2016 à 20:56, John Lenz <concavelenz at gmail.com> a écrit :

Can you reference something as to why the more obvious operators are problematic?

?.

That one (that I've used) must work, with the simple lookahead I've put in the lexical grammar, in order to continue to parse x?.3:0 as today.

?[] ?()

For those it is difficult for the parser to easily (i.e. quickly, without trying and backtracking code of arbitrary length) distinguish from the conditional operator, as in: x ?(y - 2) + 3 : 0 Also, the difference of precedence level between the two operators makes the use of a cover grammar (I think) impossible.

?:

I'm not sure what that one should be used for. (If you mean the Elvis operator, it's out of the scope of the proposal.)

# John Lenz (8 years ago)

On Wed, Feb 3, 2016 at 2:41 PM, Claude Pache <claude.pache at gmail.com> wrote:

Le 3 févr. 2016 à 20:56, John Lenz <concavelenz at gmail.com> a écrit :

Can you reference something as to why the more obvious operators are problematic?

?.

That one (that I've used) must work, with the simple lookahead I've put in the lexical grammar, in order to continue to parse x?.3:0 as today.

?[] ?()

For those it is difficult for the parser to easily (i.e. quickly, without trying and backtracking code of arbitrary length) distinguish from the conditional operator, as in: x ?(y - 2) + 3 : 0 Also, the difference of precedence level between the two operators makes the use of a cover grammar (I think) impossible.

Waldemar's example makes the problem obvious but I think we could do use, which I think is preferable to the proposed:

.? (?) [?]

?:

I'm not sure what that one should be used for. (If you mean the Elvis operator, it's out of the scope of the proposal.

yes, I meant the equivalent to:

x ?: value x == null ? x : value

# Claude Pache (8 years ago)

Le 4 févr. 2016 à 17:47, John Lenz <concavelenz at gmail.com> a écrit :

[...]

Waldemar's example makes the problem obvious but I think we could do use, which I think is preferable to the proposed:

.? (?) [?]

Yes, that syntax is possible. Whether it is preferable is a question of taste. Personally, I don’t like it:

  • I slightly prefer ?. over .? for the following reason: The ?. token may be conceptually separated in two, first the question mark which checks whether the expression at its left evaluates to null/undefined (and orders to stop processing if it is the case); then the dot which proceeds with property lookup.

  • I find that the question mark inside the brackets is out of place, as it isn’t part of the arguments (for function call) or of the expression defining the key (for property access).

[...]

yes, I meant the equivalent to:

x ?: value x == null ? x : value

I assume you meant: x != null ? x : value

Note that this is a completely different operator. For the proposed optional chaining operator, we stop processing when the LHS is null/undefined; while for the ?: null-coalescing operator, it is the other way round. Also, the precedence is not the same.

I have wilfully restricted the scope of my proposal to optional chaining only, because of its intrinsic technical complexity.

# /#!/JoePea (8 years ago)

Hello Claude, you prefer ?. over .? as an implementor (I understand what you said about the parsing). But I think as and end user of the syntax, .? over ?. makes more sense as it is easier to distinguish from the ternary operator with a float, as in x?.3:0 (we know a numerical key can't be accessed with the dot operator currently) and x?.foo:0. If we assume the existential operator doesn't allow numerical property access, then x.?3 is obviously an error (not so obvious with ?.), and we won't expect the ternary operator. Although, x.?3 also introduces the interesting possibility of allowing numerical property access as that also can't be confused with a float-in-a-ternary like x?.3 can, which would mean that with x.?3 we would not need to use [] to access numerical properties (which be convenient in the use case of the existential operator!). Something like x.?.3 would have to fail as a syntax error.

let a = [1,2,{n:1}]
console.log(a[2]) // {n:1}
console.log(a.?2) // {n:1}
console.log(a.?.2) // SyntaxError
console.log(a.?2.3) // SyntaxError: Unexpected number (the 3)
console.log(a.?2.n) // 1
console.log(a.?2.?n) // 1

/#!/JoePea

# John Lenz (8 years ago)

On Thu, Feb 4, 2016 at 10:06 AM, Claude Pache <claude.pache at gmail.com>

wrote:

Le 4 févr. 2016 à 17:47, John Lenz <concavelenz at gmail.com> a écrit :

[...]

Waldemar's example makes the problem obvious but I think we could do use, which I think is preferable to the proposed:

.? (?) [?]

Yes, that syntax is possible. Whether it is preferable is a question of taste. Personally, I don’t like it:

  • I slightly prefer ?. over .? for the following reason: The ?. token may be conceptually separated in two, first the question mark which checks whether the expression at its left evaluates to null/undefined (and orders to stop processing if it is the case); then the dot which proceeds with property lookup.
  • I find that the question mark inside the brackets is out of place, as it

isn’t part of the arguments (for function call) or of the expression defining the key (for property access).

I agree with this but I think I would get used to ".?", etc. "?.(" and "?.[" are awkward, and longer (and harder to type). ".?" "(?" and "[?" would parse easily, are short

[...]

yes, I meant the equivalent to:

x ?: value x == null ? x : value

I assume you meant: x != null ? x : value

haha, yes

Note that this is a completely different operator. For the proposed optional chaining operator, we stop processing when the LHS is null/undefined; while for the ?: null-coalescing operator, it is the other way round. Also, the precedence is not the same.

I have wilfully restricted the scope of my proposal to optional chaining only, because of its intrinsic technical complexity.

sure. that is very reasonable.

# Kevin Smith (8 years ago)

Thanks for putting this together. At first glance, I think the semantics look pretty good. The syntax still seems problematic, though, from an aesthetic point of view.

The obj ?. prop form looks natural and aligns well with how this feature appears in other languages. The other forms are less natural:

obj?.[expr] func?.(...args) new C?.(...args)

I'm not particularly convinced by any of the other syntactic variants we've seen.

That aside, I have a question about the semantics. What does this do:

({ x: 1 }).x?.y.z;

Does it throw a ReferenceError?

In general, when this feature is used in other languages, do you tend to see the optional operator stacked up for the rest of the member expression? E.g.

obj.a?.b?.c?.d

In order to avoid reference errors "in the middle"?

# Claude Pache (8 years ago)

Le 4 févr. 2016 à 21:03, Kevin Smith <zenparsing at gmail.com> a écrit :

That aside, I have a question about the semantics. What does this do:

({ x: 1 }).x?.y.z;

Does it throw a ReferenceError?

Yes: the ?. operator does not change the meaning of the subsequent . operator. I like to think of it as: the effect is local (short-circuiting aside), you are not switching between "modes". It’s a feature.

Maybe I should write a desugaring in my proposal. The expression ({ x: 1 }).x?.y.z is equivalent to:

(function () {
    var $1 = { x: 1 }).x;
    if ($1 == null)
        return undefined;
    return $1.y.z;
})()
# Claude Pache (8 years ago)

Le 4 févr. 2016 à 21:03, Kevin Smith <zenparsing at gmail.com> a écrit :

(...) The syntax still seems problematic, though, from an aesthetic point of view.

The obj ?. prop form looks natural and aligns well with how this feature appears in other languages. The other forms are less natural:

obj?.[expr] func?.(...args) new C?.(...args)

I'm not particularly convinced by any of the other syntactic variants we've seen.

Yeah, I fear that we couldn’t find a "good" syntax for those, given that obj?[expr] and func?(...args) are most probably excluded :-(

o.x?[y]+z     // should be parsed as:  (o.x?[y]) + z
o.x?[y]+z:t   // should be parsed as:  o.x ? ([y] + z) : t
# Kevin Smith (8 years ago)

Yes: the ?. operator does not change the meaning of the subsequent . operator. I like to think of it as: the effect is local (short-circuiting aside), you are not switching between "modes". It’s a feature.

Just curious: what's the rationale for that behavior, as opposed to "deep" short-circuiting? It seems like if I have a long query, like:

a.b.c.d

I think I would typically want to test that the whole query is satisfiable (not just a single term), so I'll need to write:

a?.b?.c?.d

# Claude Pache (8 years ago)

Le 5 févr. 2016 à 16:22, Kevin Smith <zenparsing at gmail.com> a écrit :

Yes: the ?. operator does not change the meaning of the subsequent . operator. I like to think of it as: the effect is local (short-circuiting aside), you are not switching between "modes". It’s a feature.

Just curious: what's the rationale for that behavior, as opposed to "deep" short-circuiting? It seems like if I have a long query, like:

a.b.c.d

I think I would typically want to test that the whole query is satisfiable (not just a single term), so I'll need to write:

a?.b?.c?.d

(In order to avoid confusion from those that read casually that thread, I recall that the proposal already features "long" short-circuiting in the sense that the end point of the short-circuiting jump is the end of the entire chain of property accesses, method calls, etc. The issue here is essentially whether a ?. should implicitly transform all subsequent . in the chain into ?..)

The general rationale is, as I've said, no modes, because modes are confusing. In the particular case, there should be testimonies that the pattern a?.b?.c?.d is both sufficiently common and annoying in order to consider an exception to that rule.

But here is a random example, where I would not use stacked ?. or something semantically equivalent:

var numberOfSelectedItems = myForm.querySelector('select[name=foo]')?.selectedOptions.length || 0;

In case myForm.querySelector('select[name=foo]') is not null, then myForm.querySelector('select[name=foo]').selectedOptions is always an HTMLCollection and has always a length property. If it is not the case, then either I made a typo, or I am testing some ancient browser that doesn’t support yet the selectedOptions property. In both cases, I want an exception to be thrown rather than an error to be dropped: it's easier to debug.

# Kevin Smith (8 years ago)

In case myForm.querySelector('select[name=foo]') is not null, then myForm.querySelector('select[name=foo]').selectedOptions is always an HTMLCollection and has always a length property. If it is not the case, then either I made a typo, or I am testing some ancient browser that doesn’t support yet the selectedOptions property. In both cases, I want an exception to be thrown rather than an error to be dropped: it's easier to debug.

Makes sense, thanks.

# Mat At Bread (8 years ago)

I'd also prefer this. Plus, one could use 'try' in an expression context as a unary operator.

// z is undefined if a or a.b or a.b.c are undefined
z = try a.b.c ;

// x is undefined if a exists but b or c don't. It throws a ReferenceError 
if a doesn't exist.
x = a.(try b.c) ;

I've not thought it through all the way, but an implementation would be an implicit catch that re-throws on anything that's not a ReferenceError, e.g.

x = a ;
try {
    x = x.b.c ;
} catch(ex) {
    if (!(ex instanceof ReferenceError))
        throw ex ;
}
# Bergi (8 years ago)

Claude Pache wrote:

.? (?) [?]

Yes, that syntax is possible. Whether it is preferable is a question of taste. Personally, I don’t like it:

  • I slightly prefer ?. over .? for the following reason: The ?. token may be conceptually separated in two, first the question mark which checks whether the expression at its left evaluates to null/undefined (and orders to stop processing if it is the case); then the dot which proceeds with property lookup.

Totally agreed.

  • I find that the question mark inside the brackets is out of place, as it isn’t part of the arguments (for function call) or of the expression defining the key (for property access).

I agree here as well, it does feel out of place, and ?[…]/?(…) would feel a lot more natural. Given that those are not feasible for parsing however, I would still prefer them

obj[?expr] func(? …args) new C(? …args)

over the proposed alternative

obj?.[expr] func?.(…args) new C?.(…args)

where the placement of the dot is just horrifying my eyes. Maybe we could at least use some other character instead of the dot?

obj?:[expr] func?:(…args) new C?:(…args)

might bear too much resemblance to the ternary, but imo the colon fits better than the dot here.

, Bergi

# John Lenz (8 years ago)

If we ever hope to include "elvis".

obj?:[expr]

would be roughly equivalent to:

obj != null ? obj : [expr]

rather than what you are suggesting here:

obj != null ? obj[expr] : undefined;

# Claude Pache (8 years ago)

Le 8 févr. 2016 à 01:16, Bergi <a.d.bergi at web.de> a écrit :

Claude Pache wrote:

.? (?) [?]

Yes, that syntax is possible. Whether it is preferable is a question of taste. Personally, I don’t like it:

  • I slightly prefer ?. over .? for the following reason: The ?. token may be conceptually separated in two, first the question mark which checks whether the expression at its left evaluates to null/undefined (and orders to stop processing if it is the case); then the dot which proceeds with property lookup.

Totally agreed.

  • I find that the question mark inside the brackets is out of place, as it isn’t part of the arguments (for function call) or of the expression defining the key (for property access).

I agree here as well, it does feel out of place, and ?[…]/?(…) would feel a lot more natural. Given that those are not feasible for parsing however, I would still prefer them

obj[?expr] func(? …args) new C(? …args)

over the proposed alternative

obj?.[expr] func?.(…args) new C?.(…args)

where the placement of the dot is just horrifying my eyes.

Personally, I'm less attached to aesthetics than legibility.

Maybe we could at least use some other character instead of the dot?

obj?:[expr] func?:(…args) new C?:(…args)

might bear too much resemblance to the ternary, but imo the colon fits better than the dot here.

Placing the colon before the interrogation mark might also work (at the condition we don’t intend to use ? as a prefix operator):

obj:?.prop     // (not needed, for symmetry)
obj:?[expr]
func:?(...args)
# Claude Pache (8 years ago)

Le 8 févr. 2016 à 19:58, John Lenz <concavelenz at gmail.com> a écrit :

If we ever hope to include "elvis".

obj?:[expr]

would be roughly equivalent to:

obj != null ? obj : [expr]

rather than what you are suggesting here:

obj != null ? obj[expr] : undefined;

We can always use ?? for that purpose, as do some languages.

However, using the same syntax as an operator (of other languages) with different semantics may be problematic.

# Alexander Mekhonoshin (8 years ago)

// Excuse my beginner’ English

I have a few (3) thoughts:

  1. binary ?.

If a === null: a?.b.c === undefined

If a?.b === null: a?.b.c // throws exception

If a?.b === null: a?.b?.c === undefined

If a === 0: a?.b.c === undefined

If a === '': a?.b.c === undefined

If a in not defined: a?.b.c // throws exception

If a === undefined but defined: a?.b.c === undefined

  1. unary ?.

window?.navigator?.toString()

browser: "[object Navigator]"

node: ReferenceError: window is not defined

here i suggest syntax for exception-slient accesing globals:

?.a === hostGlobalObject?.a

?.a?.b === hostGlobalObject?.a?.b

  1. groupped ?.()

Syntax for the full existential chain case:

.?(a.b.c) // equals with typeof a !== 'undefined' && a.b && a.b.c

I use .? here too because ?:

# Alexander Mekhonoshin (8 years ago)

??(a.b.c) is good alternative for ?.(a.b.c) as shorthand for ?.a?.b?.c

# Claude Pache (8 years ago)

Le 25 août 2016 à 16:05, Alexander Mekhonoshin <invntrm at yandex-team.ru> a écrit :

  1. unary ?.

window?.navigator?.toString()

browser: "[object Navigator]" node: ReferenceError: window is not defined

here i suggest syntax for exception-slient accesing globals:

?.a === hostGlobalObject?.a ?.a?.b === hostGlobalObject?.a?.b

For accessing the global object, I think that the current System.global proposal is a better call, because it adds no syntax. See:

tc39/proposal-global

System.global?.navigator.toString() is somewhat lengthy, but it is nevertheless simple and clear.

# Claude Pache (8 years ago)

Le 25 août 2016 à 16:05, Alexander Mekhonoshin <invntrm at yandex-team.ru> a écrit :

  1. groupped ?.() Syntax for the full existential chain case:

.?(a.b.c) // equals with typeof a !== 'undefined' && a.b && a.b.c

In other words, .?(a.b.c) (or whatever other syntax) is approximately equivalent to a?.b?.c.

The advantage of such a shortcut should be weighted against the burden of learning yet another syntax. On that subject, I recommend to read:

esdiscuss.org/topic/the

# Claude Pache (8 years ago)

Le 25 août 2016 à 17:17, Claude Pache <claude.pache at gmail.com> a écrit :

Le 25 août 2016 à 16:05, Alexander Mekhonoshin <invntrm at yandex-team.ru <mailto:invntrm at yandex-team.ru>> a écrit :

  1. unary ?.

window?.navigator?.toString()

browser: "[object Navigator]" node: ReferenceError: window is not defined

here i suggest syntax for exception-slient accesing globals:

?.a === hostGlobalObject?.a ?.a?.b === hostGlobalObject?.a?.b

For accessing the global object, I think that the current System.global proposal is a better call, because it adds no syntax. See:

tc39/proposal-global, tc39/proposal-global

System.global?.navigator.toString() is somewhat lengthy, but it is nevertheless simple and clear.

Correction: System.global.navigator?.toString()