operators to replace "== null" and "!= null"
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();
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).
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.
A simpler solution might be to treat
nullas 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.
True, but non-nullable types will likely catch most of those statically.
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.
@munificent I use a lot of nullable types (that is, types that I want "null" to be valid for).
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
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'
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.
All in all, probably not a viable syntax.
I don't mind the exact syntax - the functionality is what matters
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.
The current plan is to allow parentMap?.['href'] ?? defaultValue. The feature is planned as part of the non-nullable type feature.
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.
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
This didn't make it into the initial proposal. I think it's likely something we'll follow up with at some point.
Couldn't wait for it =)
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.
Good to know! Thank you!
Interesting.
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)
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);
Related (or dupe) of:
- https://github.com/dart-lang/language/issues/2332
- https://github.com/dart-lang/language/issues/360
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)
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.
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?
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.
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".
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.
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:
-
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? -
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 ())
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.