language icon indicating copy to clipboard operation
language copied to clipboard

operators to replace "== null" and "!= null"

Open fniemann opened this issue 6 years ago • 32 comments

Dart already provides the beloved and very useful NULL-aware operators ??, ??= and ?.

I'd really like to see operators to replace the tedious "== null" and "!= null" comparisons.

Something along the lines of:

if (??nullableObject) callFoo();
if (!?nullableObject) callFoo();

fniemann avatar Jan 23 '19 09:01 fniemann

I'm a little afraid of having too many meanings of the same operator.

Also, it's not clear to me which one is == null and which is != null, so the syntax is not self-explanatory.

(Maybe it will be less important to do this checking if we had non-nullable types).

lrhn avatar Jan 23 '19 11:01 lrhn

Personally, I don't think these operators are very useful outside of if conditions, ||, &&, and !. A simpler solution might be to treat null as falsey like most other languages do.

munificent avatar Jan 23 '19 18:01 munificent

A simpler solution might be to treat null as falsey like most other languages do.

Please don't do that, I catch all kinds of bugs via our current "it must be a boolean" rule.

Hixie avatar Jan 23 '19 20:01 Hixie

True, but non-nullable types will likely catch most of those statically.

munificent avatar Jan 23 '19 21:01 munificent

You can actually write the first line with the current null-aware operator: nullableObject ?? callFoo(); The second line would be covered by #131. #132 would allow you to execute more than one expression after the null check.

pschiffmann avatar Jan 23 '19 21:01 pschiffmann

@munificent I use a lot of nullable types (that is, types that I want "null" to be valid for).

Hixie avatar Jan 26 '19 07:01 Hixie

You can actually write the first line with the current null-aware operator: nullableObject ?? callFoo(); @pschiffmann actually, would it make sense to extend the ternary operator to something like: a ?? x : y

fniemann avatar May 08 '19 07:05 fniemann

What would be absolutely amazing is a hybrid of ?: and ?? object.property ?? currency.format(object.property) : 'N/A' Or even object.property ?? currency.format($) : 'N/A'

duzenko avatar Jul 05 '19 09:07 duzenko

We probably can't use e1 ?? e2 : e3 because it would be ambiguous. The expression {e1 ?? e2 : e3} is already a map literal {(e1 ?? e2) : e3}, but would also be parsable as a set literal {(e1 ?? e2 : e3)}. I'm also sure it will make parsing harder for the conditional operator ?/: because e1 ? e2 ?? e3 : e4 would prefer to match e4 to e2 ?? e3. In this case, it mustn't, but for e1 ? e2 ?? e3 : e4 : e5 it should. That means that the parsing requires look-ahead to see if there is a later :.

All in all, probably not a viable syntax.

lrhn avatar Jul 05 '19 09:07 lrhn

All in all, probably not a viable syntax.

I don't mind the exact syntax - the functionality is what matters

duzenko avatar Jul 05 '19 09:07 duzenko

Introduce at least something for Map, so we can simplify JSON parsing from parentMap != null ? parentMap['href'] : defaultValue to something like parentMap?['href'] || defaultValue or similar.

VladimirCores avatar Aug 10 '19 13:08 VladimirCores

The current plan is to allow parentMap?.['href'] ?? defaultValue. The feature is planned as part of the non-nullable type feature.

lrhn avatar Aug 11 '19 12:08 lrhn

What if I want to check a function variable is not null, then call it?

Normally I would write:

if (foo.bar != null) {
    foo.bar();
}

But I would like to write like:

foo.bar?()

This is very useful when using optional callbacks in Flutter.

rockingdice avatar Aug 16 '19 15:08 rockingdice

What if I want to check a function variable is not null, then call it?

We have discussed supporting .?() for that case:

foo.bar?.();

I'm not sure if that made it into the NNBD proposal or not. If not, it's a reasonable change we could add later.

@leafpetersen

munificent avatar Aug 16 '19 17:08 munificent

This didn't make it into the initial proposal. I think it's likely something we'll follow up with at some point.

leafpetersen avatar Aug 16 '19 20:08 leafpetersen

Couldn't wait for it =)

rockingdice avatar Aug 17 '19 02:08 rockingdice

For the call-if-not-null case you can use an existing mechanism, the call method:

class A {
  void Function() bar() => null;
}

main() {
  var foo = A();

  // Rather than this: ..
  if (foo.bar != null) foo.bar();
  // .. you could use this:
  foo.bar?.call();
}

This works because invocation of a function f in general may be expressed as f(...) or as f.call(...). It's more concise than the if, but also more robust: It might be a bug to evaluate foo.bar twice because of side-effects.

eernstg avatar Aug 19 '19 08:08 eernstg

Good to know! Thank you!

rockingdice avatar Aug 19 '19 09:08 rockingdice

Interesting.

tjx666 avatar Apr 21 '21 19:04 tjx666

coming up with a good syntax is a problem here. Here's my take:

if (obj👍) callFoo(obj);
if (obj👎) callSomethingElse();

The feature will become an instant hit! 🥇

if (&obj) callFoo(obj);
if (!&obj) callSomethingElse();

where & operator means &x <=> (x!=null)

duzenko avatar Apr 22 '21 13:04 duzenko

I've also found this to be quite a common pattern. Here's a potential solution. Would be nice to have as part of a main library somewhere.


extension CallOnNullAsNull on Function {
  // callIfNotNull
  R? calln<T, R>(T? arg) => switch (arg) { T value => this(value), null => null };
}

extension IsNotNullThen on Object? {
  R? isThen<T, R>(R Function(T value) fn) => switch (this as T?) { T value => fn(value), null => null };
  R? nullThen<R>(R Function() fn) => (this == null) ? fn() : null;
}

//e.g.
bool test(int value) => (value == 10);
bool? eg = [0, 1].elementAtOrNull(2).isThen(test);

FireSourcery avatar May 22 '24 19:05 FireSourcery

Related (or dupe) of:

  • https://github.com/dart-lang/language/issues/2332
  • https://github.com/dart-lang/language/issues/360

FMorschel avatar Dec 04 '24 19:12 FMorschel

Both forms can be expressed without "if":

nullableObject == null ? callFoo() : (); // read () as "DO NOTHING"
nullableObject != null ? callFoo() : (); 

It's only for those who (like me) dislike single-statement if (cond) expr; You can write the first one as nullableObject ?? callFoo() (read ?? as OR ELSE), but the above version might be more appealing for those who like symmetry.

(() is a 0-tuple, which is a good token standing for "do nothing" IMO)

ghost avatar Dec 07 '24 12:12 ghost

The main problem with if (nullableObject != null) callFoo(); for me is the length.

In JS it's very short: if (nullableObject) callFoo(), but I agree treating null as falsey is very error-prone (imo).

Could the following mean if == null?

if? (birthday) {
  birthday = getBirthday();
  check(birthday);
}

and the following mean if != null?

[ if! (message) Text(message) ]

Considering if (x case! y) {} and unless (x case y) {} are 2 alternatives being favored over if! (x case y) {} in the guard-clause issue.

Wdestroier avatar Dec 07 '24 19:12 Wdestroier

To reach the parity with JS, it suffices to introduce a "truthiness" operator like ^obj, which converts null to false and non-null to true - then you will be able to write ^obj && expr and ^obj || expr. Are there any philosophical objections to this?

ghost avatar Dec 07 '24 21:12 ghost

You can write that operator today, you just don't get any promotion.

extension on Object? {
  bool operator ~() => this != null;
}

The philosophical objection to making a language feature for it would be that it's not worth it to avoid writing != null. While it could be "nice", it won't pay for even the opportunity cost.

Allowing you to actually abstract over promotion would be more powerful, and would allow people to write more features like this of they want to. It's also highly non-trivial.

lrhn avatar Dec 08 '24 07:12 lrhn

Oh, I've just realized that the problem lies in the second operand of &&. That is, if you write obj != null && callFoo(), the second operand (callFoo()) has to be a boolean expression. There's nothing to be done about it in a backward-compatible manner. So we are left with obj != null ? callFoo() : () or something, if we want to avoid "if".

ghost avatar Dec 08 '24 14:12 ghost

If that's the alternative, then I wouldn't try to avoid the if. That's a cure that's worse than the illness.

Just write

if (variable != null) use(variable);

It's idiomatic and easy to read.

Converting it to a conditional expression statement with a dummy branch is basically obfuscation.

Or use:

extension on Object {
  T eval<T>(T expression) => expression;
}

as

  variable?.eval(use(variable))

which does nothing if variable is null, but promoters it to non-nullable in the following selectors when it's not null.

Can call it something else, like isNotNull, if you prefer. Works on non-variable expressions too, just can't promote those.

lrhn avatar Dec 08 '24 18:12 lrhn

if (variable != null) use(variable);

Indeed, it's idiomatic (because you declared it so, which some style guides disagree with), and arguably readable, but to me, it feels rather ugly . It's not always easy to say why exactly you do not like something, and maybe the following are not the real reasons, but:

  1. don't you find it strange that you can write cond ? callX() : callY() with 3 operands, but when there are just 2, you have to use a totally different syntax?

  2. what if you have to write both branches?

if (cond) callX(); // do you write it like this? Or how?
else callY();

Do you think writing it as cond ? callX() : callY() is obfuscation?

As for the dummy branch: you can internalize it very quickly, and :(); at the end will read as a single symbol. What is missing is the ability to return from ?: expression, but I remember you were willing to add support for return, right?

(Can dart provide some constant of type Never? Then it could be used instead of ())

ghost avatar Dec 09 '24 01:12 ghost

don't you find it strange …

No. The ?/: syntax is an expression which must have a value. You cannot omit one of the branches, because then it doesn't have a value if the condition would take that brandy. If you want to omit a branch, then it shouldn't be an expression at all. A condition with only one branch is an if with no else.

That is also why you should never use

cond ? callX() : callY();

as a statement. It sends the wrong signal, that this is an expression with a value that matters, when in fact it doesn't. Always use

if (cond) {
  callX();
} else {
  callY();
}

For that, because that looks like what it is: one of two calls which don't return a value.

Using () as a dummy value that is going to be ignored in a void context is worse than using null, which at least has always meant "no value".

You don't want an expression of type Never, because that currently means an expression that throws. It's an expression that must not be executed, and here you do take the other branch in some cases, you just don't want to do anything. But "do" is the word you use for statements, not expressions.

Don't use an expression to do a statement's work, it makes the reader think that you intended to have a value.

A shorter syntax for expr != null wouldn't be a problem if used with if, but I don't expect it to be worth the syntax, effort or opportunity cost.

lrhn avatar Dec 09 '24 07:12 lrhn