-
Notifications
You must be signed in to change notification settings - Fork 226
Description
Introduction
One particular kind of expression comes up frequently, and there have been some discussions about how it could be expressed more concisely (I can't find a dedicated issue, though). The expression is condition ? expression : null
.
The behavior of this expression is "transistor-like" in the sense that it uses the boolean value of condition
to turn on or off the evaluation of the expression, yielding the value of the expression if it was evaluated, and null otherwise.
This issue is a proposal that we should make :
and the last expression optional in the grammar rule about conditional expressions, and specify that : null
is implied when that part is omitted.
Existing approaches
It is crucial that the expression
is not evaluated in the case where the boolean value is false: That value will not be used anyway (so we don't want to waste resources on computing it), and it may even give rise to run-time failures if it is evaluated (e.g., r.hasFoo ? r.foo : null
where r.foo
might throw when r.hasFoo
is false).
We could consider reversing the order of the operands (a bit like the reverse conditional statements in some languages a la doThis() if somethingIsTrue();
), and use an extension method:
extension<X> on X {
X? operator ^(bool b) => b ? this : null;
}
void main() {
final x = 'Hello!';
print(x ^ true); // Prints 'Hello!'.
print(x ^ false); // Prints null.
}
The operator ^
is basically only defined for numbers and booleans themselves, so it might be OK to use it for this purpose on all other types. However, this is not good enough because it will evaluate the left operand, no matter what.
We could also use an operator like ~
on bool to turn it into null when we want the value (that is, true should be mapped to null), and then we'd use ~b ?? e
. However, if we do this then there is no good value to map false
to: It should be null, because that's the value that the whole expression ~b ?? e
should have when the boolean is false, but if we do that then we'll just evaluate e
and yield that value again.
extension on bool {
bool? operator ~() => this ? null : false /* what else?! */;
}
void main() {
print(~true ?? 'Hello!'); // Prints 'Hello!', as it should.
print(~false ?? 'Hello!'); // Prints 'false'. Should have been 'null'.
}
Proposal
We change the grammar such that the two last elements in the conditional expression are optional:
<conditionalExpression> ::=
<ifNullExpression>
('?' <expressionWithoutCascade> (':' <expressionWithoutCascade>)?)?
This should not create difficulties during parsing because <conditionalExpression>
only occurs in the right-hand side of grammar rules of top level constructs: <expression>
, <expressionWithoutCascade>
, <initializerExpression>
, and <cascade>
(where it's followed by ?..
or ..
). In other words, if we don't see a colon then the conditional expression definitely omits the last part, and if we do se a colon then we have already decided that we will commit to parsing a conditional expression in cases where an expression can be followed by a colon (that is, in various map literal related constructs).
A conditional expression b ? e
where ':' <expressionWithoutCascade>
has been omitted is treated as b ? e : null
.
For example:
void main() {
print(true ? 'Hello!'); // Prints 'Hello!'.
print(false ? 'Hello!'); // Prints null.
// A typical example, assuming that the undefined names have suitable declarations.
// "Check if we have a video, then use its length, otherwise bail out with a null."
var length = myProtoAsset.hasVideo() ? protoAsset.video.length;
}