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

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

Open
fniemann opened this issue Jan 23, 2019 · 31 comments
Open

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

fniemann opened this issue Jan 23, 2019 · 31 comments
Labels
request Requests to resolve a particular developer problem

Comments

@fniemann
Copy link

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();
@lrhn
Copy link
Member

lrhn commented Jan 23, 2019

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

@munificent
Copy link
Member

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.

@Hixie
Copy link

Hixie commented Jan 23, 2019

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.

@munificent
Copy link
Member

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

@pschiffmann
Copy link

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.

@Hixie
Copy link

Hixie commented Jan 26, 2019

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

@fniemann
Copy link
Author

fniemann commented May 8, 2019

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

@duzenko
Copy link

duzenko commented Jul 5, 2019

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'

@lrhn
Copy link
Member

lrhn commented Jul 5, 2019

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.

@duzenko
Copy link

duzenko commented Jul 5, 2019

All in all, probably not a viable syntax.

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

@VladimirCores
Copy link

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.

@lrhn
Copy link
Member

lrhn commented Aug 11, 2019

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

@rockingdice
Copy link

rockingdice commented Aug 16, 2019

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.

@munificent
Copy link
Member

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

@leafpetersen
Copy link
Member

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

@rockingdice
Copy link

Couldn't wait for it =)

@eernstg
Copy link
Member

eernstg commented Aug 19, 2019

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.

@rockingdice
Copy link

Good to know! Thank you!

@tjx666
Copy link

tjx666 commented Apr 21, 2021

Interesting.

@lrhn lrhn added the request Requests to resolve a particular developer problem label Apr 22, 2021
@duzenko
Copy link

duzenko commented Apr 22, 2021

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)

@FireSourcery
Copy link

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

@FMorschel
Copy link

FMorschel commented Dec 4, 2024

@tatumizer
Copy link

tatumizer commented Dec 7, 2024

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)

@Wdestroier
Copy link

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.

@tatumizer
Copy link

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?

@lrhn
Copy link
Member

lrhn commented Dec 8, 2024

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.

@tatumizer
Copy link

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

@lrhn
Copy link
Member

lrhn commented Dec 8, 2024

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.

@tatumizer
Copy link

tatumizer commented Dec 9, 2024

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

@lrhn
Copy link
Member

lrhn commented Dec 9, 2024

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

@tatumizer
Copy link

tatumizer commented Dec 9, 2024

The difference between

if (cond) expr;

and

if (cond) expr1;
else expr2;

is that the latter contains 2 doses of the former. Now it's just a matter of personal sensitivity: for me, even one dose is one too many. 😄

I agree that expressions are always called for "result", but it depends on your definition of "result". In this case, the "result" is the completion of the execution of the expression. Which, by the way, is not always guaranteed in dart, hence the debate about unawaited futures.

The () would be completely useless unless you give it some interpretation. I suggest that "do nothing" interpretation is a good one, especially given that dart doesn't have a standard symbol for do-nothing. null may (or may not) mean "nothing", but it's not the same as do-nothing. Considering that "nothing" and its counterpart "do-nothing" are both fundamental philosophical concepts (there's a large body of work dedicated to them, spanning 2000 years of intense thinking by great minds), it would stand to reason to support them officially out-of-box. The argument for () would be that it already looks like calling "nothing", which makes it an ideal candidate for "do-nothing".

(Obligatory quote)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
request Requests to resolve a particular developer problem
Projects
None yet
Development

No branches or pull requests