dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.08k stars 1.56k forks source link

More null-aware operators #27716

Open mehmetf opened 7 years ago

mehmetf commented 7 years ago

It would be nice to have a shorthand for this:

val == null ? null : method(val);

This is the opposite of val ?? method(val) which only returns method(val) when val is null.

We love the null friendly operators so it bugs me a lot that I can't do this. Normally you would not need it if method can do ?. everywhere but sometimes the shorthand helps. For instance, some operators are not null friendly:

double convertFromMicro(Int64 value) =>
    value == null ? null : value.toDouble() / _microDollars;

value?.toDouble() / _microDollars would blow up if value is null.

lrhn commented 7 years ago

It is a pattern that occurs reasonably often. It's worth considering. It's a "null guard" rather than a "null replacement", similar to the use of the JavaScript && operator in x && x.foo() (which is what x?.foo() does in Dart) and x && foo(x) (which is the case that Dart doesn't have a shorthand for).

"All the good operators are taken!" :)

floitschG commented 7 years ago

Fwiw, we are considering shortcutting all following member calls, when the first ?. fails.

That is o?.foo.bar would be correct (and not require a ?. for the second . anymore). In that case we should just continue doing this for operators too, and your example could be simplified:

double convertFromMicro(Int64 value) => value?.toDouble() / _microDollars;

It wouldn't work for the direct operator case, though: value / 499.

lrhn commented 7 years ago

I personally don't think that extending the reach of the ?. operation to surrounding operators is viable.

It's possible, defining it so that any member called on the result is also ignored (which ends at the point where you use the value itself for assignment or as a function parameter), but I think it will be too confusing. I can't decide, of the top of my head, what all of the following would mean:

Still, if we can make it work, it would be great.

mehmetf commented 7 years ago

I would also vote against handling this via extending the reach of ?. operator. I believe it would create readability issues and might lead to more problems than it solves. I like the current explicit way of null guarding.

For instance, people might get comfortable and start writing formulas like ((val?.toDouble() / 42) * 170).toFloor(). It isn't clear to me at all how far ?. reaches. I would rather have an explicit piece of code that says when val is null, return null.

My original thought was to use the bang op in conjunction with ?? which universally means 'not'. Basically, if LHS null then DO NOT eval the RHS.

val !?? ((val.toDouble() / 42) * 170).toFloor()

We are getting dangerously close to emojis :-).

jamesderlin commented 5 years ago

Also see https://github.com/dart-lang/language/issues/360.

jamesderlin commented 3 years ago

My original thought was to use the bang op in conjunction with ?? which universally means 'not'. Basically, if LHS null then DO NOT eval the RHS.

val !?? ((val.toDouble() / 42) * 170).toFloor()

We are getting dangerously close to emojis :-).

Or ??!, and Dart could pretend to have C trigraphs. ;)

Anyway, this probably doesn't need more data points for motivation, but an inverted ?? operator would address https://stackoverflow.com/questions/67136658/.

lrhn commented 3 years ago

An "xargs"/"applyTo" like extension function can work, but it means introducing a closure, which is heavyweight in both syntax and execution overhead. It does solve the issue of delimiting the code affected by the check, something most proposals fail to do, and which I think is essential to making the feature practically useful. The ?. works because users understand how far it reaches (they're probably slightly wrong, but equally likely they'll never notice).

It works today, which is awesome compared to all other suggestions so far. 👍

Naming. Is. Hard. (So is punctuation, but at least this avoids using new operators). Maybe call it to: y?.to(print). It's such a generic operation, and one that shouldn't be too verbose, so the name should be short and general.

lrhn commented 3 years ago

Most likely we can get inlining most of the time. We're calling a static function (extension methods are static functions) so that's ripe for inlining, and then it should be fairly simple to see that the function literal can be inlined too. It's just not something that can be promised in general since the language doesn't require it. Some compilers might optimize for size over speed, and inlining can cause code duplication (it won't here, but the compiler probably needs to try before it can see whether it blows up or not).