Closed madskonradsen closed 5 years ago
The issue with a?()
and a?[b]
is that it is difficult to distinguish (both for humans and machines) from the ? :
operator. See https://github.com/claudepache/es-optional-chaining/issues/3
Sigh. Alrighty then. Closing up :)
I think this should be reconsidered. Such expression is used in many languages already.
I'm reopening then. Some people(in other issues) have expressed interest in having this debate again...
The conceptors of C# did apparently face a similar parsing issue around ?(
. From https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-conditional-operators:
You need to explicitly call the
Invoke
method because there is no null-conditional delegate invocation syntaxPropertyChanged?(e)
. There were too many ambiguous parsing situations to allow it.
@claudepache you're right about C#.
Although it works in Swift but they have their own constraints that make it feasible to disambiguate optional chaining from ternary expression, such as spaces are taken into account, i.e:
true?"whoa":"boo"; // error
true ?"whoa":"boo"; // ok
true ? "whoa" : "boo"; // ok, readable
It's possible to support the var?()
or var?[]
syntax by forcing the blank space before ?
to disambiguate the intent:
a?()?[b]+c?.d:e?.f; // error
a?() ? [b]+c?.d : e?.f; // ok
a?() ?[b]+c?.d : e?.f; // ok, less readable
a?() ?[b]+c?.d:e?.f; // ok, unreadable
In case if there is no space before ?
the error is most likely to follow due to orphaned :
since ?
falls into optional chaining.
To conclude my take on that:
a?
is optional chaininga ?
is a part of expression with ternary operator.After spending some time on that I must admit that ?
is a breaking change and ?.
is not. It's safer to stick to ?.
for the sake of backward compatibility.
Personally I would separate ?
followed by a dot proposal—which is not controversial—from ?
followed by [
or (
which, IMOH, should be breaking changes. The babel plugin should support ?.
right away and this repository should strip anything related to things akin to ?[
and ?(
so that new proposals for them may be championed.
I think consistency is important.
IMHO, the .
should strictly be reserved for property accessors only. A ?
operator makes perfect sense in the a?.b?.c
cases, however, to shim it into a ?.
operator in a?.[]
and a?.()
I fear will bring a lot of confusion to the feature.
This also creates an inconsistency that I haven't seen mentioned. Given the operator ?.
:
Case 1: a()
becomes a?.()
Case 2: a[]
becomes a?.[]
Case 3: a.b
becomes a?.b
- What happened to the dot property accessor?
The operator ?.
has consumed the dot property accessor in Case 3. No other operators or notation in the language consume sibling syntax in this way. Inserting the operator directly into the original syntax would produce a?..b
as a
+ ?.
+ .b
.
It seems as if the original idea was to support a?.b
and it was then shimmed into other cases as the ?.
operator because there was a dot in the original use case. I don't think a?..b
is a good idea, just as I don't think a?.()
and a?.[]
are good ideas.
The a?.b
case is a great idea because it is saying "object a
is optional, if available I'm going to access property b
via the dot property accessor". However, this would then mean the operator is actually ?
and not ?.
.
a?.b
onlyIn kind with @Mouvedia, I'd almost rather see initial support for the optional chaining operator in dot properties only and let the later proposals add the other cases. Similar to what was done with the proposed export
extensions.
If the proposal must support all use cases, then I think:
This leads me to an operator like ??
:
a??()
a??[]
a??.b??.c
This is not 100% ideal but it is at least consistent. Again, I'd go for the basic support only first though.
In any case ☮️ ❤️ , thanks
A two-char operator is possible, yes. There is however a tension between keeping a better syntax for the common case (a?.b
) and having an absolutely consistent syntax in all cases.
Anyway, although it is technically possible, we should avoid to use the same symbol ??
for both Optional chaining and Null coalescing, because it has opposite semantics:
a ?? b
: evaluates tob
if a
is null;a?.b
: evaluates to a.b
if a
is not null.Possible alternative: a:?.b
— a:?[b]
— a:?(b)
About the suggestion to initially support a?.b
only.
I’m against it. According to statistics in #17, in a sample of CoffeeScript code, there are significant uses of a?(b)
(12%) and a?[b]
(5%), so it is important to support those cases as well. If we support a?.b
only, syntax issues for other cases will not become more easily resolvable: those problems are known since long ago, probably several years, and no solution that satisfies everyone has been found yet.
It is best to decide now (I mean, during the next few months) for a syntax for all cases, whether it is uniform or slightly inconsistent with better look for the common case.
initially support
a?.b
only
I am talking about the babel plugin. We shouldn't push stuff like ?.[]
in the wild even at stage 1.
It is best to decide now … for a syntax for all cases
That's alright by me but the current proposal do favor certain syntaxes that are controversial and inconsistent with ?.
as explained just above. IMHO the parser should have to struggle not us.
I understand the unfortunate reasons why a?[..] isn't going to happen - but I'm with levithomason here. While a??.b isn't ideal, I think it is at least tolerable if it means we don't have to have a?.[foo]
In my opinion, a?.[foo] is pretty confusing as it is easy to accidentally read the ?. operator as two separate operators - especially for us poor souls that have written lots of coffeescript.
In my opinion, a?.[foo] is pretty confusing as it is easy to accidentally read the ?. operator as two separate operators - especially for us poor souls that have written lots of coffeescript.
Well, as someone who as written lots of JavaScript, I find CoffeeScript semantics of ?
quite confusing at first sight:
a ?[b] # in JS: a != null ? a : [b]`
a?[b] # a != null ? a[b] : undefined;
null ?[b] # [b]
null?[b] # syntax error
(null)?[b] # undefined
a = { 'x': 42 }
a ?['x'] # a
a?['x'] # 42
Also:
42 // comment # Math.floor(42/comment)
a ? b : c # a != null ? { b: c } : undefined
I’m not saying that we should ignore coffeescripters’ habits. But if you switch between CoffeeScript and JavaScript, you must already know that there are many things that work differently.
I like @levithomason's proposal for ??.
/??()
/??[]
. While slightly verbose, consistency across all the cases (member access, invocation, indexing) is important, and an opportunity to improve over the support in other languages.
The double-question-mark syntax also nicely parallels nullary-coalescing ??
.
The double-question-mark syntax also nicely parallels nullary-coalescing ??.
As I said above, for me it is a problem rather than an advantage, because the null-coalescing operator has an opposite meaning. (I know that CoffeeScript uses the same symbol for both operators, but CoffeeScript is somewhat confusing.)
Eh, sure, I can see the potential for confusion. What about using ?:
for nullary-coalesce, to parallel the conditional operator (for which it's sugar)?
Otherwise, we need a different two-character combination for optional chaining, since single special characters all present parsing difficulties. I suppose I could get used to something like ?!.
/?![]
/?!()
, which vaguely parallels TypeScript's non-null assertion (expression!
).
@claudepache thanks for the counter points, I'm in agreement with you. I was also not aware of the complimentary nullary-coalescing operator proposal. Per the stated goal of that project:
...the intent is to provide a complementary operator to the optional chaining operator.
I'd be in favor of using the cleanest syntax for optional chaining (perhaps ??
) and updating nullary coalescing to play nicely.
Looking at this operator in other languages for inspiration, here are some options:
?:
- Somewhat nice as it plays on the ternary syntax, but might also get pretty confusing.|?
- Also nice as it conjures ideas of the current ||
operator currently used in similar scenarios.Of these, I'd vote for 2) |?
strictly out of personal preference. It feels more like I'm saying "or this default" opposed to ?:
which looks and feels like a mess to my eyes:
const val = a??.b |? 'a was null'
// vs
const val = a??.b ?: 'what is happening'
Optional ??
Or default |?
@tvald the elvis operator should probably mirror the usage of the ternary syntax and hence not be limited to undefined
and null
. But that's a bit off topic.
@levithomason your |?
proposal is interesting.
If consistent application of truthy
/falsy
vs nullish
is important, and I agree that it is, there should be more of an effort to standardize this across the many proposals for shorthand operators.
This is the set of relevant operators that I'm aware of:
base | truthy /falsy |
nullish |
|
---|---|---|---|
coalesce | x ? x : y |
x \|\| y (alt: x ?: y ) |
x \|? y (alt: x ?? y ) |
guard | x ? y : x |
x && y |
x ?! y (link) |
chain | x ? x.y : x |
- | x??.y |
It feels like the set of possibilities for guard/coalesce are currently a mess, but that we want some recognizable parallel with the chaining operator. I think designing each of these operators independently will result in confusion, as they all inhabit corners of the same space.
edit 2017/08/08: simplified table by removing extraneous ternary row
I'm not sure I follow some of the examples in the table. My proposal is ??
for optional chaining and |?
for null coalescing (borrowed from F#). Is this mostly a heads up or is there an alternative proposal in there I've missed? Apologies for the lack of understanding on my part.
Is this mostly a heads up or is there an alternative proposal in there I've missed?
Nullish guard shorthand (?!
) was raised over in the proposal for nullish coalesce. (link)
Two comments above, @Mouvedia argued that elvis (?:
) should be a truthy/falsy coalesce.
I'm just documenting the complete space of possible and proposed adjacent operators.
This is how I would fill in the chart, for optimal symmetry:
base | truthy /falsy |
nullish |
|
---|---|---|---|
coalesce | x ? x : y |
x \|\| y |
x ?\| y |
guard | x ? y : x |
x && y |
x ?& y |
chain | x ? x.y : x |
- | x??.y |
(If this is too far astray from the original discussion, I can open a separate issue either here or in the nullary-coalese repo.)
Using ??
for optional chaining and ?|
for null coalescing seems a good solution for me. (For ?&
, you should find use cases and share them in https://github.com/gisenberg/proposal-nullary-coalescing/issues/4.)
A potential issue, is that ??
is quite frequently used for null coalescing in other mainstream languages. That doesn’t bother me, because if you mistakenly try to use ??
for null-coalescing, in most cases, you will get a syntax error.
@Mouvedia
|?
proposal is interesting. That's how F# does it.
No, your link just shows an example of custom operator definition.
I'd be ok with ??.
, if only to get rid of the unsightly optional call in a chain:
// this is confusing
obj?.foo?.(args).prop
// a little better
obj??.foo??(args).prop
@jridgewell The goal of ??
is precisely to be able to write a??[b]
and a??(b)
.
Yah, that's what I meant. Just brining up call-in-a-chain syntax, and how ugly it is currently (it looks like another member access to me).
No, your link just shows an example of custom operator definition.
@claudepache corrected.
@tvald I love the reflection and the logic behind ?:
> ||
> ?|
and ?:
> &&
> ?&
.
It's self evident 👍
Another possibility is to keep ??
for nullish-coalescing, and use ?&
for optional chaining:
a?&.b
— a?&[b]
— a?&(b)
(And in case you ask me about nullish guard: not needed.)
And in case you ask me about nullish guard: not needed.
For the ones wondering, he explains it here.
What about using parentheses to make it clear that it's necessary ? In the same way that you need to distinguish
test => ({ foo: 'bar' });
and
// label foo
test => { foo: 'bar' };
Since ?
is not a valid variable you could be explicit about it:
a?.b
// equivalent to
a(?).b
// optional property access (bracket notation)
a(?)[expr]
a(?)['b-b']
// optional function or method call
a(?)(...args)
There is no conflict with existing code*—since it would throw a syntax error—and it's a familiar pattern that has been used before for the concise body of the arrow function. On the plus side we won't have to retract the babel 7 plugin.
Am I missing something obvious ?
* example by @claudepache
@Mouvedia
What about using parentheses to make it clear that it's necessary ?
Since
?
is not a valid variable you could be explicit about it:a?.b // equivalent to a(?).b // optional property access (bracket notation) a(?)[expr] a(?)['b-b'] // optional function or method call a(?)(...args)
Definitely no.
If optional parentheses were allowed around operators: yes, sure. But until now, they are allowed around expressions only; so that introducing optional parentheses here is an innovation, and that innovation is not a good idea because:
a?.b
(why put useless parentheses?), but a(?)[expr]
and a(?)(...args)
; so you haven’t resolved the inconsistent-syntax issue.a?.b
and a(?).b
.But you have created yet another issue, namely two ways to write the same operator: a?.b and a(?).b.
That's a feature to me: a shorthand when the parentheses can be omitted. That's probably a question of perpective but I can't find any real downsides to my proposal. I am biased clearly.
Another possibility is to keep
??
for nullish-coalescing, and use?&
for optional chaining:
I also like this, 2 chars, doesn't conflict with existing notation, doesn't consume sibling syntax. Anything down this road and semi-consistent with other language paradigms I think is the right direction.
Giving my vote to this option for the sake of progress.
I like ?&.
/?&()
/?&[]
for optional chaining. They can be interpreted as a new set of compound operator where x ?&. y
is equivalent to x ?& x.y
, if nullish guard ever comes into existence. :P
I've come to like ?|
more than ??
for null coalesce, as it more obviously parallels the existing ||
operator. There doesn't seem to be a clear consensus among other languages for the "right" operator, so we're not bound by any precedent. I think ?|
, together with ?&.
, is an improvement over the decisions in other languages.
how about .?
to get rid of lookaheads and mixups with other operations
I don't think that generalizes well to function invocation and indexing:
foo.?bar
foo(bar)?
foo[bar]?
Yep, agreed. Let's keep the ?.
syntax. Also, it is pretty well-established syntax in other languages.
On this syntax:
a(?).b a(?)[b] a(?)(c)
Definitely no. If optional parentheses were allowed around operators: yes, sure. But until now, they are allowed around expressions only; so that introducing optional parentheses here is an innovation, and that innovation is not a good idea because:
Would we change our minds if this wasn't an "optional" thingy but rather using a three-character (?)
operator rather than ?
or ??
?
It doesn't look as bad to me (from a reader's perspective, and from a writer's perspective this is such a deliberate decision that I wouldn't mind typing the extra character).
a(?).b.c.d(?).e.f.g.h(?)(1, 2).i.j.k(?)[l].m.n.o.p
I guess the worst/most confusing part would be that it could look like a function call rather than an accessor ... so a(?) kind of seem like you are calling a with a parameter ...
As you mention, the (?) syntax isn't easy to distinguish from function calls which is also why I would prefer something along the lines of ?? or even ?.
Right, I think that's fair.
I really buy the consistency argument that was made earlier, so I'm leaning towards either:
??
ora?[b]
and a?()
Has anyone looked more closely into checking for disambiguation feasibility here?
See newly proposed syntax in #34
@ljharb and I had a chat on Babel's Slack recently, proposing a??.b
, a??[b]
and a??(b)
, and changing null coalescing to a ??: b
ljharb [1:15 AM]
`??` would be nice for consistency with the operator, but would probably break because `a??(b)`
ljharb [1:24 AM]
personally i’m not really convinced we need optional method calling, and i really liked `?.` and `?[`, and consistency would dictate `?(`, but i grasp why that might not work grammatically
jridgewell [1:38 AM]
What’s wrong with `a??(b)`
ljharb [1:39 AM]
@jridgewell if `a ?? b` is a separate operator (a separate proposal) that’d clash
and `a ?? b` is basically intended to be `a == null ? a : b`
jridgewell [1:40 AM]
I actually like it, mainly because `a.b?.().c` looks like crap (edited)
Hmm, we could change that to `?:`
ljharb [1:40 AM]
so for one, i think https://babeljs.slack.com/archives/C45BCCB0T/p1505805405000087 is paramount for this proposal - and for two, i think the `??` operator is an absolute necessity
Jordan Harband
@gisenberg iow `x[`, `x(`, and `x.` all need to have the same tokens for `x` imo
nah, `?:` has precedent
in PHP `a ?: b` is `a ? a : b`, which in JS is already `a || b`
(in PHP, `a || b` is in JS `!!(a || b)` or `a ? !!b : false`)
jridgewell [1:43 AM]
Yah, but that means we don’t need PHP’s semantics. :wink:
ljharb [1:43 AM]
well yes, but `a ?: b` just makes so much sense as being `a ? a : b`
it’s a ternary with the middle omitted
it wouldn’t be sensible to have it mean anything else
jridgewell [1:43 AM]
Yah, I see your point.
jridgewell [2:08 AM]
How about `??:` for it?
`a ?: b` -> ` a ? a : b`
`a ??: b` -> `a != null : a : b`
Then we get `a??.b` and `a??[b]` and `a??()`
ljharb [2:10 AM]
and `a ??: b` for the operator?
jridgewell [2:10 AM]
Yah.
ljharb [2:10 AM]
it’s not the horriblest idea
i’ll have to noodle on it, but other than “it’s a heck of a lot of question marks”, it might work
jridgewell [2:11 AM]
I think the biggest goal it to avoid `a.b?.().c`.
I really can’t understate how much I hate that.
It looks like someone screwed up and put parenthesis in the chain.
ljharb [2:11 AM]
why?
well lol, any “optional chaining” operator is because someone screwed up :stuck_out_tongue:
i’m in favor of this proposal because it makes a really really crappy pattern much less crappy, not because “needing it” isn’t a code smell.
personally i’m not really convinced we need optional method calling, and i really liked
?.
and?[
, and consistency would dictate?(
, but i grasp why that might not work grammatically
I for one would second this: if removing the requirement to cover ?( for method/function calls gets us closer to get ?. and ?[ that's a trade-off that I think is worth taking.
it could look like a function call rather than an accessor
That's a fair point.
Looking further into implementation feasibility of a?[b] and a?()
Isn't this settled already? If not Id be very interested.
a ?: b
->a ? a : b
?:
should be reserved for that use case.
JS already has || for that; ?: should never be added to JS.
@ljharb I know that I am just saying that coming from another language that has ?:
you would be very surprised, in a bad way, if it was used for something else. Hence it needs to be reserved but not implemented.
Agreed.
IMHO, there should some criteria for reaching a conclusion to this convo. Ideas?
how about if no new revelations are here by oct 22, since we passed sep 22 already.
Arbitrary enough for me! I haven't seen any new proposals or angles presented in a while.
I'm very very glad to see this become resurrected. One thing I'm curious about is why you chose to have a dot after the question mark in case of function invocation and why you have a dot between question marks and square bracket notation. It seems strange to me, but perhaps i'm too biased by having written too much CoffeeScript.