microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.08k stars 12.37k forks source link

Suggestion: "safe navigation operator", i.e. x?.y #16

Closed RyanCavanaugh closed 4 years ago

RyanCavanaugh commented 10 years ago

Current Status

Open questions


C# and other languages have syntax sugar for accessing property chains where null (or in our case, undefined) might be encountered at any point in the object hierarchy.

var x = { y: { z: null, q: undefined } };
console.log(x?.y?.z?.foo); // Should print 'null'
console.log(x?.baz); // Still an error
console.log(x.y.q?.bar); // Should print 'undefined'

Need proposal on what exactly we should codegen, keeping in mind side effects of accessors.


Edit by @DanielRosenwasser February 27, 2018: This proposal is also called the "null propagation" operator.

hsablonniere commented 8 years ago

@schungx Fair enough, but it would help for the weird possibilities like this : a?['b'] or this a?().

AshCoolman commented 8 years ago

+1 ?

a?['b'] and a?() behave nicely in coffeescript, and they look good to me.

But then again, I might just be coffeescript-blind.

p-mcgowan commented 8 years ago

+1 Ruby just implemented the existential operator https://twitter.com/mikepack_/status/657229703443451904. Typescript needs this too, regardless of the specific syntax.

andriniaina commented 8 years ago

This would be make code cleaner πŸ‘

agu-z commented 8 years ago

+1 Need this!

svekl commented 8 years ago

Please, implenent ?. operator, as C# does

pavlovt commented 8 years ago

+1 I've been missing this for a long time

osi-oswald commented 8 years ago

+1 so ugly to write: someVariable && someVariable.someMember when you could write: someVariable?.someMember

maku commented 8 years ago

+1, this would be great.... (most wanted feature for me)

aere-github commented 8 years ago

+1, it's a good idea! Only, if the object is complicated, the expression will be full of ?. after each property (var result=myValue?.a?.b?.c?.d?.e;) when I need to recieve a value of the last one (var result=?myValue.a.b.c.d.e;).

bryanmig commented 8 years ago

+1 - This is arguably one of the greatest features of CoffeeScript and is by far my team's most desired TypeScript feature after converting much of our code from CS to TS.

falsandtru commented 8 years ago

+1 however, this is too complex:

var x = { y: { z: null, q: undefined } };
var z: x|y|z = x?.y?.z;

I like this:

var x = { y: { z: null, q: undefined } };
var z: z|void = x?.y?.z;
schungx commented 8 years ago

x?.y?.z's type is always the type of the z field. Of course the type must be nullable and the actual value may be null. If it is non-null, then it must be of the type of the z field.

gaurav21r commented 8 years ago

+1 This would go well with Typescript's vision of easing development of large scale complex JS Projects.

Any updates on this? Is this a case of the community voting for this feature for it to be considered? Or has it been considered but there are some engineering challenges?

DanielRosenwasser commented 8 years ago

No updates so far because introducing new expression-level syntax is dangerous without some sort of proposal from the ECMAScript committee.

See https://github.com/Microsoft/TypeScript/issues/16#issuecomment-57645069.

kitsonk commented 8 years ago

Here is a more current discussion on existential operators (lots of thoughts, but doesn't look like a lot of action):

msklvsk commented 8 years ago

where should I write "+1" to help bring it to ES?

kitsonk commented 8 years ago

@msklvsk ESDiscuss

RyanCavanaugh commented 8 years ago

Closing this for now. Since there's not really anything TypeScript-specific that would require this at expression level, this kind of big operator change should happen at the ES spec committee rather than here.

The general tripwires for re-evaluating this would be a concrete ES proposal reaching the next stage, or a general consensus from the ES committee that this feature wouldn't happen for a long time (so that we could define our own semantics and be reasonably sure that they would "win").

luisrudge commented 8 years ago

life sucks

RyanCavanaugh commented 8 years ago

Deleting standalone :+1:s. Please use the GitHub reactions feature or send flowers and candy to your nearest TC39 representative.

algesten commented 7 years ago

Once I got used to it in coffeescript and Swift, there's no going back. TS is dead to me as it currently stands.

loretoparisi commented 7 years ago

@algesten I can agree about swift if we consider the language itself, not sure about long term coffescript support (I use CoffeScript in production projects). We may agree to be sure of language analytics trends as for June 2016 like this where we can clearly see what's going on to coffescript recently, while TypeScript has seen the fastest growth in the past years:

TypeScript: Outside of Go or Swift, the fastest growing language we’ve observed in recent years is TypeScript. The Microsoft-backed JavaScript superset and Angular 2 foundation has made significant gains for the second consecutive quarter, jumping from #31 to #26. That was the biggest single change in any Top 30 language, and the second largest jump overall (Standard ML, 7 spots). At #26, in fact, TypeScript is now tied with Erlang, one spot behind Powershell and four behind CoffeeScript, which is just outside the Top 20. The question facing the language isn’t whether it can grow, but whether it has the momentum to crack the Top 20 in the next two to three quarters, leapfrogging the likes of CoffeeScript and Lua in the process.

Until 2014 the coffescript trends was more than positive as you can look here, that is when the decline started.

yuvalrakavy commented 7 years ago

With the new typescript 2.0 strict null (undefined) checking, it is a must, because otherwise you cannot use function chaining!

aluanhaddad commented 7 years ago

This should be in the next version of ECMAScript. Considering this would be even more useful in vanilla JavaScript than TypeScript and considering that the implementation would naturally be a check for undefined or null as opposed to a truthy check, it should be trivial for TC39 to add.

The simple implementation and one which would be idiomatic in JavaScript, would simply be to short circuit when encountering either null or undefined, and return undefined.

@bterlson does this make sense? What are the odds of such a proposal being accepted?

yuvalrakavy commented 7 years ago

One of the great benefits of typescript is "the future is given to you today". This is one of the features that I was surprised that are not already there.

I hope that it will be added soon as function chaining is popular idiom and will strict null checking it is no longer work unless the ?. operator is added.

TS 2 wonderful null checking made it from a nice to have to must have!

cervengoc commented 7 years ago

Reading through this thread I'm kind of surprised how this doesn't get more attention from the maintainers.

In an ideal world TypeScript should lead the way for ES and not reversely. Seriously, where would be TypeScript now if the TS team would always have waited for ESx to propose and finalize a feature or a syntax?

This syntax is really a "must have", as others have pointed it out. It even received some good proposals in this thread so far. I think the implementation should match these expectations.

What do you think the parts are which can lead to disagreements when ES will specify it?

osi-oswald commented 7 years ago

For a?.b I do not see any conflicts with existing (and future?) syntax. If the parser finds the token ?. it can treat it as 'safe navigation operator' (with expectations as described by @cervengoc).

Syntax conflicts arise only when allowing a?(b) and a?[b], since these could also be interpreted as the start of a ternary ?: operator expression. But for starters I think these could be put aside and supporting just the a?.b syntax would already make a lot of developers happy!

RyanCavanaugh commented 7 years ago

In an ideal world TypeScript should lead the way for ES and not reversely.

In terms of expression-level syntax, we absolutely disagree. There's a committee driving a standard for a reason -- not so that one-off actions by one player can unilaterally decide the future of JavaScript.

This syntax is really a "must have", as others have pointed it out.

If this is a must-have for TypeScript, it's a must-have for JavaScript as well! Again, take your concerns to the ECMAScript committee.

What do you think the parts are which can lead to disagreements when ES will specify it?

At a minimum, I think there will be disagreement about whether the syntax short-circuits to null or undefined when encountering those values, or always short-circuits to undefined. There is also going to be some contention around whether or not some form of bracketed syntax is supported. There's also the question of what the behavior of a?.b.c is. There's also the question of ?. vs .? vs a.b?. There's the question of what the effect of this on the delete operator is.

The ES DIscuss thread on this is over 100 comments long. There's no lack of ambiguity! It's easy to look at one example in isolation and think there can't be tons of corner cases. There are. That's probably why no one has championed this at TC39 yet and also why we're not rushing to add a feature that has lots of ambiguity around how it should behave.

cervengoc commented 7 years ago

I apologize, I didn't read through the mentioned thread, I will definitely have a look at it to see more.

We see this a bit differently. About the committee, in my honest opinion this is one of the biggest reasons why JavaScript will never be good. Just for an example, many of the most successful software what I've seen (like Total Commander or IrfanView) are successful because they are maintained and designed by ONE person, and not by a *committee". Of course this is not a completely correct example. But I'm almost sure, that if for example you alone would have designed the complete ES6, then the world would be a better place now.

Also, the ambiguities which you've mentioned are in 99% theoretical things and kind of irrelevant from the developer's side. Who would care about what it returns, null or undefined? Just pick one, and we will use it like that.

All in all, you and that committee are on a different side than most of us, and things from that side are usually more complex than they really are. And this can lead to some contra-productivity to say the least. Don't take it personally, but according to my experience in general, some people would be better come out of the conference room more often and have a look at some code.

Of course no offense, don't take anything personally, I have a huge respect to you and all the TS team, because you did revolutionize many developers' client-side development including myself, and thank you for all your work. We're just a bit disappointed about this specific one I guess.

cervengoc commented 7 years ago

One last thought. I've ran through the mentioned ES thread, and one thing is completely sure: they are overcomplicating it. They want to design something which is good for every scenario.

I personally would be completely satisfied and happy with a conditional member access operator. Don't need conditional invocation, conditional index signature, don't need to support every fancy scenario which is valid JS code. That's all. But instead, they will likely keep sitting there and discuss how to do everything at once, which is a great plan, but we will have nothing at the end of the day.

kitsonk commented 7 years ago

All in all, you and that committee are on a different side than most of us, and things from that side are usually more complex than they really are.

I don't think you are reflecting the true status of Ryan or TC39 accurately. Ryan, and the TypeScript team, have set out very clear design goals for TypeScript. One of the original and still very current goals is that TypeScript is a superset of JavaScript. Not the language people would like it to be (e.g. Dart, Haxe). When it come to syntax, the TypeScript team have learned the hard way the cost of pre-inventing it (e.g. modules). We are headed headlong into a challenge with private members of classes too, where the proposed ES syntax is totally incompatible with the syntax TypeScript uses. Why? Because what may look uncomplicated on the surface is impossible to achieve given the run-time challenges of a language.

TC39 has "saved" JavaScript in my opinion. ES4 was abandoned, not because it lacked good, innovative ideas, but because it would break the internet. TC39 got themselves into shape, they have shared and been totally open in their discussions and how they make decisions and delivered us ES2015, which is a lot like ES4, but didn't break the internet. It is amazing that we essentially have JavaScript run-times that run code from 10 years ago just fine, but support many significant improvements to the language. ES2016 was the lull before the storm. ES2017 has a "reasonable" amount of functionality and change and a clear governing process that heads the right direction.

So being on the "different side" of things clearly has worked, in my opinion. Which beats expediency of "must have" features.

cervengoc commented 7 years ago

@kitsonk I didn't mean "different side" negatively, and especially I didn't mean to degrade the work what was put into TypeScript or ES6. What's more, the best thing in TypeScript IMO is that it really had and has a clear design goal, and it's well protected against becoming a havoc like many other opensource stuff.

I just wanted to tell that this very feature is a clear example of where a group of genius people will end up overthinking and overcomplicating things, instead of just going the easy simple way, and accept some limitations like not supporting invocations or index signatures, etc. Someone in that forum even suggested using this syntax in assignments, which is kind of crazy. I still think that this phenomenon is contra-productive in this sense.

I understand that on your side it's a pain that for example private members became incompatible with the final ES6 concept. But on the other hand, we HAD it. Way before ES6. And that's the main point from our side. Roughly speaking, we don't care about how you manage to emit the appropriate code for it, we're just happily using it. Same with modules, and everything. We (or at least I) didn't see those pains what you're talking about it, we were always happy with private members or modules.

This particular feature is in CoffeScript as I read about it. Why do we, simple developers always have to make compromises when choosing a platform/library/plugin, etc.? I mean always. This is kind of annoying. Here we have a great language, which has great potential, which completely leaves every other participants behind (including ES!), and which has successfully revolutionized a huge part of client-side development, and when it comes to this "simple" feature (I mean the member access part at least), we hear that it won't be implemented until ES commits to this.

gisenberg commented 7 years ago

I wanted to give folks a quick heads up that this feature moved from stage 0 to stage 1 at today's TC39 meeting.

Relevant commit: https://github.com/tc39/proposals/commit/cb447642290a55398d483f5b55fb7f973273c75d Meeting agenda: https://github.com/tc39/agendas/blob/master/2017/01.md

luisrudge commented 7 years ago

wow! that's huge!

RyanCavanaugh commented 7 years ago

Also worth a link to https://github.com/claudepache/es-optional-chaining

RyanCavanaugh commented 7 years ago

Some "surprises" I see here (not saying I disagree, just things that we probably would have done differently if we had done this earlier):

gisenberg commented 7 years ago

I'd expect to see change in those areas between now and stage 2.

algesten commented 7 years ago

I think coffeescript got it right.

a?.b.c throws if b is undefined.

a?() and a?[0] are both good.

jaredru commented 7 years ago
  • Propagation in chained dots: a?.b.c.d will not throw if b and c properties are undefined
  • Propagation in presence of parens: even (a?.b).c will not throw if b is undefined
  • Propagation even occurs on method calls: a?.b.c().d will return undefined if the c invocation returns null

Those points don't seem accurate to me. From the proposal:

a?.b.c().d      // undefined if a is null/undefined, a.b.c().d otherwise.
                // NB: If a is not null/undefined, and a.b is nevertheless undefined,
                //     short-circuiting does *not* apply
RyanCavanaugh commented 7 years ago

Wow, I totally misread that. You're right. Updating

sergeysova commented 7 years ago

@algesten from original proposal:

a?.()

b?.[0]
algesten commented 7 years ago

sweet. the operator can sort of be though of as ?. then.

gisenberg commented 7 years ago

There's some additional conversation happening over here: https://github.com/estree/estree/issues/146

karol-depka commented 7 years ago

A quote that could apply well: Β«Make simple things easy, and difficult things possibleΒ». Thus perhaps, support the most common cases well, while skip (at least initially) the complicated/rare cases, while they are still allowed to be "done manually" with longer (existing) syntax.

cedvdb commented 7 years ago

Just my two cents

let a = b?.c?.d?.e;

to:

let a;
try{
   a = b.c.d.e;
}catch(e){
   a = undefined;
}
RyanCavanaugh commented 7 years ago

@cedvdb totally different semantics -- exceptions thrown in getters should not cause coalescing

cedvdb commented 7 years ago

@RyanCavanaugh yeah.. I didn't think this through.

MgSam commented 7 years ago

Is this on the radar for implementation now, or will the TS team wait for the ES proposal to move further along?

mhegazy commented 7 years ago

It is on our short list; we still have a few questions/concerns but you should see movement on this in the comming month.