Skip to content

Enable a transistor-like behavior of a boolean value #4519

@eernstg

Description

@eernstg

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    brevityA feature whose purpose is to enable concise syntax, typically expressible already in a longer formsmall-featureA small feature which is relatively cheap to implement.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions