tc39 / proposal-optional-chaining

https://tc39.github.io/proposal-optional-chaining/
4.94k stars 75 forks source link

Dot after question mark #5

Closed madskonradsen closed 5 years ago

madskonradsen commented 7 years ago

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.

a?.()
a?.[b]
claudepache commented 7 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

madskonradsen commented 7 years ago

Sigh. Alrighty then. Closing up :)

pronebird commented 7 years ago

I think this should be reconsidered. Such expression is used in many languages already.

madskonradsen commented 7 years ago

I'm reopening then. Some people(in other issues) have expressed interest in having this debate again...

claudepache commented 7 years ago

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 syntax PropertyChanged?(e). There were too many ambiguous parsing situations to allow it.

pronebird commented 7 years ago

@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:

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.

Mouvedia commented 7 years ago

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.

levithomason commented 7 years ago

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 ?..

Proposals

Initially support a?.b only

In 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.

Always two character

If the proposal must support all use cases, then I think:

This leads me to an operator like ??:

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

claudepache commented 7 years ago

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:

Possible alternative: a:?.ba:?[b]a:?(b)

claudepache commented 7 years ago

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.

Mouvedia commented 7 years ago

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.

lsim commented 7 years ago

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.

claudepache commented 7 years ago

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.

tvald commented 7 years ago

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 ??.

claudepache commented 7 years ago

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.)

tvald commented 7 years ago

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!).

levithomason commented 7 years ago

@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.

Proposed nullary coalescing update

Looking at this operator in other languages for inspiration, here are some options:

  1. ?: - Somewhat nice as it plays on the ternary syntax, but might also get pretty confusing.
  2. |? - 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 |?

Mouvedia commented 7 years ago

@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.

tvald commented 7 years ago

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

levithomason commented 7 years ago

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.

tvald commented 7 years ago

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.

tvald commented 7 years ago

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.)

claudepache commented 7 years ago

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.

claudepache commented 7 years ago

@Mouvedia

|? proposal is interesting. That's how F# does it.

No, your link just shows an example of custom operator definition.

jridgewell commented 7 years ago

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
claudepache commented 7 years ago

@jridgewell The goal of ?? is precisely to be able to write a??[b] and a??(b).

jridgewell commented 7 years ago

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).

Mouvedia commented 7 years ago

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 👍

claudepache commented 7 years ago

Another possibility is to keep ?? for nullish-coalescing, and use ?& for optional chaining:

a?&.ba?&[b]a?&(b)

(And in case you ask me about nullish guard: not needed.)

Mouvedia commented 7 years ago

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

claudepache commented 7 years ago

@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:

Mouvedia commented 7 years ago

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.

levithomason commented 7 years ago

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.

tvald commented 7 years ago

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.

devsnek commented 7 years ago

how about .? to get rid of lookaheads and mixups with other operations

tvald commented 7 years ago

I don't think that generalizes well to function invocation and indexing:

foo.?bar
foo(bar)?
foo[bar]?
levithomason commented 7 years ago

Yep, agreed. Let's keep the ?. syntax. Also, it is pretty well-established syntax in other languages.

samuelgoto commented 7 years ago

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 ...

madskonradsen commented 7 years ago

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 ?.

samuelgoto commented 7 years ago

Right, I think that's fair.

I really buy the consistency argument that was made earlier, so I'm leaning towards either:

Has anyone looked more closely into checking for disambiguation feasibility here?

madskonradsen commented 7 years ago

See newly proposed syntax in #34

jridgewell commented 7 years ago

@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.
samuelgoto commented 7 years ago

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.

Mouvedia commented 7 years ago

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.

ljharb commented 7 years ago

JS already has || for that; ?: should never be added to JS.

Mouvedia commented 7 years ago

@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.

ljharb commented 7 years ago

Agreed.

levithomason commented 7 years ago

IMHO, there should some criteria for reaching a conclusion to this convo. Ideas?

devsnek commented 7 years ago

how about if no new revelations are here by oct 22, since we passed sep 22 already.

levithomason commented 7 years ago

Arbitrary enough for me! I haven't seen any new proposals or angles presented in a while.