Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use ".?" operator #2

Closed
naholyr opened this issue Feb 5, 2016 · 45 comments
Closed

Use ".?" operator #2

naholyr opened this issue Feb 5, 2016 · 45 comments
Labels

Comments

@naholyr
Copy link

naholyr commented Feb 5, 2016

By swapping characters you can greatly simplify parsers job and eliminate every potential conflict.
It allows expansion to arrays and function even if final syntax would be weird :)

@claudepache
Copy link
Owner

I don’t understand what is exactly the issue you are trying to resolve.

By swapping characters you can greatly simplify parsers job and eliminate every potential conflict.

The current syntax uses a simple lookahead in the lexical grammar to avoid conflict. By swapping the characters, you just avoid that lookahead. I’m not sure that that alone "greatly simplifies parsers job". Or am I missing something?

It allows expansion to arrays and function even if final syntax would be weird :)

The current proposal has already a working-although-weird expansion to arrays and functions.

@nhz-io
Copy link

nhz-io commented Feb 5, 2016

parser should work hard so we would work less harder.
coffee script solves this issue by eliminating ternary operator completely, and using if then else instead.
you can't have this here, and imho, it will take a good fight for this proposal to pass.
(I would really like existential operator of some form in es6, improves readability and shortens the lines)

@yuchi
Copy link

yuchi commented Feb 5, 2016

I’m with @nhz-io in thinking the syntax is weird. IMHO the weirdier is o?.[k].

I propose another candidate which is ??:

obj??             =>   (typeof obj !== 'undefined' && obj !== null && obj !== undefined)
obj??.key         =>   ((obj??) ? obj.key : undefined)
obj??[key]        =>   ((obj??) ? obj[key] : undefined)
fun??(...a)       =>   ((fun??) ? fun(...a) : undefined)
obj.fun??(...a)   =>   ((obj.fun??) ? obj.fun(...a) : undefined)

// and obviously...
obj??.fun??( ...a )   =>   ((obj??) && (obj.fun??) ? : obj.fun(...a) : undefined)

@nhz-io
Copy link

nhz-io commented Feb 5, 2016

I propose another candidate which is ??

Yeah, i had this one in mind as well, this is actually cleanest approach and no collisions with anything and no lookahead

@nhz-io
Copy link

nhz-io commented Feb 5, 2016

This ?? completely eliminates issues with ternary, the other big issues left is making them accept it :P

@naholyr
Copy link
Author

naholyr commented Feb 5, 2016

I really like this ?? approach, very generic, very readable, very likable. I'll fight for this one instead of my .? :)

parser should work hard so we would work less harder.

Yeah but when it comes to propose new syntax, I think it's good to keep in mind that the less hard the parser will have to work, the less impact on performance this syntax will have, and the more chance it will have to be adopted.

@claudepache
Copy link
Owner

The ?? token works well by itself. However, it is used in several other languages for the null-coalescing operator, which has different semantics.

@yuchi
Copy link

yuchi commented Feb 5, 2016

@claudepache If you’re talking about this then… what about this(wild idea):

a ?? b   =>   ((a??) ? a : b)

@nhz-io @naholyr Thank you! Very glad to contribute!

@yuchi
Copy link

yuchi commented Feb 5, 2016

@claudepache Actually ?? is null-coalescing only in C# (which is a pretty big market by itself) but there’s ?: in PHP and others which looks nice.

@yuchi
Copy link

yuchi commented Feb 5, 2016

@claudepache Argh a ?? b is a bad idea:

a ?? b   =>   ((a??) ? a : b)
a??b     =>   ((a??) ? a : b)

a??[k]   =>   ((a??) ? a : [k])  // this or…
         =>   ((obj??) ? obj[key] : undefined) // this?

@claudepache
Copy link
Owner

@claudepache Actually ?? is null-coalescing only in C# (which is a pretty big market by itself) but there’s ?: in PHP and others which looks nice.

Also used in Swift and in PHP 7. (The ?: operator of PHP is more like the || operator of JavaScript.)

@claudepache Argh a ?? b is a bad idea:

a ?? b   =>   ((a??) ? a : b)
a??b     =>   ((a??) ? a : b)

That is approximatively the semantics of the null-coalescing operator, which is completely different from optional chaining. You can’t indeed use the same operator for both operations.

@yuchi
Copy link

yuchi commented Feb 5, 2016

That part was just to make it clear the possible collisions.

@davidyaha
Copy link

Swift language has these proposed tokens ?., ?[, ?(.
Which totally makes sense to me.. the question mark is used as an addition to the regular and "unsafe" versions of MemberExpression and CallExpression.

Also, I suppose that only adding an optional flag to those types will suffice to make the AST transform use them.

The only problem being the ternary operator, which Swift in contrast to coffee script has left for your free usage. So we should try to make it work regardless, in my opinion.

@claudepache
Copy link
Owner

The tokens ?[ and ?( won’t work well, because of interaction of the conditional operator ? : and backward compatibility constraints. For instance, you have to distinguish between:

a?[b] + c: d // interpreted as: a ? ([b] + c) : d

(whose meaning cannot be changed) and:

a?[b] + c; // should be interpreted as: (a?[b]) + c;

Note in particular the difference of precedence level between ? : and ?[. I expect that it would be difficult for a parser to analyse that efficiently (i.e., quickly); and even if this problem could be solved, it could be confusing for the user.

The syntax ?.[ and ?.( was chosen as a least bad option under difficult constraints. However, I have not yet written down my arguments behind that statement (which is the main reason why this issue is still open).

@davidyaha
Copy link

Hey @claudepache,

Thanks for your response!

The tokens ?[ and ?( won’t work well, because of interaction of the conditional operator ? : and backward compatibility constraints. For instance, you have to distinguish between:

a?[b] + c: d // interpreted as: a ? ([b] + c) : d`

IMHO this should not be allowed. I understand that it is a breaking change, thus it may not be accepted, but for the most part, the vagueness of this syntax is mostly harming and should be avoided.

Also, a simple restriction of making this symbol whitespace intolerant, will eliminate most syntax obscurities.

I suggest adding to the token types file 3 new tokens that will be parsed with question special readToken function (similar to that of dot ). It will check for following character being either ., ( or [.
After resolving the tokens, parsing of expressions can be done pretty easily by updating the subscript expression parse method and call expression parse method to check for cases of the new tokens.

In the more general sense, it means that the new tokens are preceding the ternary operator, and that lines of the sort you've specified above will be syntax error for that implementation.

@claudepache
Copy link
Owner

a?[b] + c: d // interpreted as: a ? ([b] + c) : d

IMHO this should not be allowed. I understand that it is a breaking change, thus it may not be accepted, but for the most part, the vagueness of this syntax is mostly harming and should be avoided.

Backward compatibility is quite a hard requirement on the web. There are very plausible use cases such as useArray?[foo]:foo (whether it is good style is not the question), which we cannot conceivably break (and browser vendors will refuse to break, anyway).

@davidyaha
Copy link

Hmm yeah I now realised the problem with that example.
And you think due to minification we cannot allow for whitespace separation to solve this problem?

If that's the case then the only possible syntax is one that allows for the ternary operator to have precedence over the optional chaining. or as you've suggested have a non conflicting syntax.

What do you think of changing the following line https://github.com/babel/babylon/blob/master/src/parser/expression.js#L158

to a condition where if there is no colon found you will than try to parse optional subscript?

As far as I see on the current babylon implementation, this is not something that is thought of as bad implementation.

My main argument for preferring this syntax over another is that it fits well with the non optional member expression. Also it is to say that we are not optimizing before checking the impact of said change on the parser's performance.

On the browser implementation end, we could optimize by other ways like making sure that those obscure sections are easily resolved by adding parenthesis on the expression.

@Alxandr
Copy link

Alxandr commented Feb 13, 2017

C# also has ?., ?[ and ?(, as well as ternary ? :, and they apparently are doing fine. Maybe look at how they define the four?

@hax
Copy link

hax commented Feb 13, 2017

I like the idea of ??. For null-coalescing operator, we could use ??? or ??: or ?|| ...

@Alxandr
Copy link

Alxandr commented Feb 13, 2017

@hax how would you handle array lookup then?

@claudepache
Copy link
Owner

C# also has ?., ?[ and ?(, as well as ternary ? :, and they apparently are doing fine. Maybe look at how they define the four?

@hax Does C# uses [ ... ] for array literals or some other literal value? If not, they don’t have the parsing issue that JS would have for ?[. If yes, we may be interested to explore how they handle this case. (Note that parsing should be not only possible, but also reasonably efficient.)

@hax
Copy link

hax commented Feb 13, 2017

@Alxandr See @yuchi 's comment: #2 (comment)

@claudepache
Copy link
Owner

I like the idea of ??. For null-coalescing operator, we could use ??? or ??: or ?|| ...

The issue with ??. is not technical but social. I foresee confusion among developers if we use ?? for something different from what is common in other languages.

@Alxandr
Copy link

Alxandr commented Feb 13, 2017

@claudepache C# uses [..] for array access (and arbitrary property access).

Example:

var firstAgeOr5 = people?[0]?.age ?? 5;

IDEone link: https://ideone.com/0IyxDW

@hax
Copy link

hax commented Feb 13, 2017

@claudepache Yes I agree it's better to stick on similar syntax but x.?[prop] also not align to other languages. And not every language use ?. for example Ruby use &., perl use .?. So I think if JS choose a little different syntax is ok.

@hax
Copy link

hax commented Feb 13, 2017

@claudepache And I remember I have seen a language use ?? for optional chaining but forget which language 🤔

Anyway, @yuchi 's proposal is eventually ??. 😃

@hax
Copy link

hax commented Feb 13, 2017

Note there are other options in the old discussion at https://esdiscuss.org/topic/existential-operator-null-propagation-operator includes .? (as the title of this issue), ?? and prefix version of ? and even !! from microsoft/TypeScript#12099 .

@ProdigySim
Copy link

ProdigySim commented Feb 23, 2017

Does C# uses [ ... ] for array literals or some other literal value?

To confirm, they do not. Array literals/expressions in C# use { ... } along with other syntax. So they avoid this issue.

@Alxandr
Copy link

Alxandr commented Feb 23, 2017

Ah. Sorry, I misread. Didn't see that the question was of array literals.

@leodutra
Copy link

leodutra commented Apr 20, 2017

Using .? does not avoid the fact it could be a ternary with a missing property name, so the lookahead would occur anyways.

C# has both obj?.prop and ternary evaluation. This MUST NOT be an issue.
Compiled, sure, but "If they can we can" is the V8 principle.

@bathos
Copy link

bathos commented May 2, 2017

Questions of preference for a particular token aside, x?.3:0 is currently a valid ternary expression and this proposal would change that as currently specced. I’m pretty sure that there’s never been a backwards incompatible change made to the lexical grammar in JavaScript’s 22 years, and I doubt it would be considered justified here — "don’t break the web" is a pretty high priority, right? I would second @yuchi’s ?? suggestion as a way to avoid this while still giving the operator a pretty intuitive symbol.

Nevermind, I now see the lookahead being referred to is to make an exception for parsing ?. as the operator for this case specifically. A bit odd but not backwards incompatible.

@naholyr
Copy link
Author

naholyr commented May 2, 2017 via email

@Wedvich
Copy link

Wedvich commented May 2, 2017

Personally I think ?. makes more semantic sense than .?. I intuitively interpret person?.name as

if person exists, give me the name property

but person.?name as

if the name property exists on person, give me its value

The second one feels a bit awkward, and with that interpretation it'd also be redundant unless there are multiple levels of chaining (person.?name would be equivalent to person.name), so it's a more confusing syntax to me. Your mileage may vary.

@claudepache
Copy link
Owner

Personally I think ?. makes more semantic sense than .?.

So do I, and it is the reason why that form was chosen rather than this one.

But sure, if there is feedback from implementors that this is too hard to parse, alternative syntaxes will be considered.

@leodutra
Copy link

leodutra commented May 2, 2017

@bathos const, let, ()=>, **... backwards incompatible changes made to the lexical grammar in less than 5 years.

@leodutra
Copy link

leodutra commented May 2, 2017

@Wedvich @claudepache I agree with the syntax, not with hard to implement.

This is a sample of lookahead
/(\w+)[\s\S]*?\?(?=[\s\S]*?\.[\s\S]*?(\w*))/im.exec('obj?.prop')

If is possible with RegExp, it is possible programmatically.

@claudepache
Copy link
Owner

const, let, ()=>, **... backwards incompatible changes made to the lexical grammar in less than 5 years.

@leodutra Those were not backward-incompatible (except for an edge-case involving let [ in non-strict mode), in the sense that only invalid syntax became valid. Maintaining backward compatibility, i.e. not changing the meaning of already valid syntax, has resulted in minor tweaks in the grammar for some of those new forms (such as forbidding line-terminators in some places). Backward-compatibility is a hard requirement on the web.

@bathos
Copy link

bathos commented May 2, 2017

@leodutra There is a distinction between the lexical and syntactic grammars in ES. If you need information about an arbitrarily distant token in order to lex the token you’re "at", that is "out of band" to the lexical grammar and must be achieved by switching goal symbols from the parser (lexing and parsing are tightly coupled in ES > 2). If that were introduced here, then one would need to add additional goal symbols to the lexical grammar. That has been done before — first for regular expression literals and second for template spans. But each additional lexical ambiguity expands the set of goals potentially by a lot. In this case I’m pretty sure that would mean adding four new ones:

InputElementDiv
InputElementDivOrTernaryColon
InputElementRegExp
InputElementRegExpOrTernaryColon
InputElementRegExpOrTemplateTail
InputElementRegExpOrTemplateTailOrTernaryColon
InputElementTemplateTail
InputElementTemplateTailOrTernaryColon

Edit: on reflection, it might mean adding only two. The InputElementRegExpOrTemplateTailOrTernaryColon and InputElementTemplateTailOrTernaryColon symbols may not be needed, because any position where } would begin a template span or tail would not be a position that permitted ternary colon by definition. So adding just two I guess isn’t so bad, though I think "not adding new goal symbols" is a pretty substantial nice-to-have from any new syntax proposal.

@leodutra
Copy link

leodutra commented May 3, 2017

@claudepache Ah... you are speaking about not breaking existing code. Right, but no proposal here, till now, would break existing code. It would not work in old engines, as let, const and lambdas... that's what I mean.

If we are not ok with lookaheads, only a single symbol or an totally new symbol for JS can evade lookaheads.

Pick yours: #, @, §, &, ...

user#props ? true : false
user??props ? true : false
user?.props ? true : false

I don't care about the lexer/parser. Its devs shall sweat blood and pain, as any dev shall do for top grade stuff. The important part is JavaScript pushes the best of known C-like languages.

C# ?. is clean and very known.

An Optional proposal would be very welcome as, widely, most functional languages relies on this for feature rich optional chaining.

@leodutra
Copy link

leodutra commented May 3, 2017

By the way, I'm not a C# fan. But I can't deny the greatness of their chaining and Lisp (Although this last one is constantly raped by bad approaches).

More about Option, for those who may not know it.

@bathos
Copy link

bathos commented May 3, 2017

I don't care about the lexer/parser. Its devs shall sweat blood and pain, as any dev shall do for top grade stuff.

I don’t think it’s a question of implementation difficulty? Complexity maybe. What you described implied a "bigger touch" to the lexical grammar than the proposal as it currently stands; meanwhile ?? and similar solutions represent very small touches. I’m not saying it’s a bad idea — just pointing out that such concerns do end up being factors when proposals are being reviewed.

@leodutra
Copy link

leodutra commented May 3, 2017

@bathos I agree Option Type is a greedy eye on conditionals.

C# has different meaning for ??... it works more like ours || (or).

An option for ?. would be the requirement for no spacing between the two chars, behaving like the spread operator (... always together). This would speed up things.

@claudepache
Copy link
Owner

An option for ?. would be the requirement for no spacing between the two chars,

There is already no space allowed between the two chars. The token ?. is defined by the lexical grammar, where spaces are significant.

@claudepache
Copy link
Owner

I'm closing this issue, as I won’t change the syntax, and it is not possible to satisfy everyone anyway. Any choice has its own pros and cons.

If you have a strong feeling about the result, you may discuss it either on es-discuss or on the official repo.

@phidias
Copy link

phidias commented Jul 28, 2017

Created this method which you might find useful (it's type-safe btw):

/**
 * Type-safe access of deep property of an object
 *
 * @param obj                   Object to get deep property
 * @param unsafeDataOperation   Function that returns the deep property
 * @param valueIfFail           Value to return in case if there is no such property
 */
export function getInSafe<O,T>(obj: O, unsafeDataOperation: (x: O) => T, valueIfFail?: any) : T {
    try {
        return unsafeDataOperation(obj)
    } catch (error) {
        return valueIfFail;
    }
}

And you can use it like this:

getInSafe(sellTicket, x => x.phoneDetails.imeiNumber, '');

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests