rfc:match_expression_v2

This is an old revision of the document!


PHP RFC: Match expression v2

Proposal

This RFC proposes adding a new match expression that is similar to switch but with safer semantics and the ability to return values.

$expressionResult = match ($condition) {
    1, 2 => foo(),
    3, 4 => bar(),
    default => baz(),
};

Differences to switch

Return value

It is very common that the switch produces some value that is used afterwards.

switch (1) {
    case 0:
        $y = 'Foo';
        break;
    case 1:
        $y = 'Bar';
        break;
    case 2:
        $y = 'Baz';
        break;
}
 
echo $y;
//> Bar

It is easy to forget assigning $y in one of the cases. It is also visually unintuitive to find $y declared in a deeper nested scope. match is an expression that evaluates to the result of the executed arm. This removes a lot of boilerplate and makes it impossible to forget assigning a value in an arm.

echo match (1) {
    0 => 'Foo',
    1 => 'Bar',
    2 => 'Baz',
};
//> Bar

No type coercion

The switch statement loosely compares (==) the given value to the case values. This can lead to some very surprising results.

switch ('foo') {
    case 0:
      echo "Oh no!\n";
      break;
}

The match expression uses strict comparison (===) instead. The comparison is strict regardless of strict_types.

echo match ('foo') {
    0 => "Never reached\n",
};

No fallthrough

The switch fallthrough has been a large source of bugs in many languages. Each case must explicitly break out of the switch statement or the execution will continue into the next case even if the condition is not met.

switch ($pressedKey) {
    case Key::RETURN_:
        save();
        // Oops, forgot the break
    case Key::DELETE:
        delete();
        break;
}

This was intended to be a feature so that multiple conditions can execute the same block of code. It is often hard to understand if the missing break was the authors intention or a mistake.

switch ($x) {
    case 1:
    case 2:
        // Same for 1 and 2
        break;
    case 3:
        // Only 3
    case 4:
        // Same for 3 and 4
        break;
}

The match expression resolves this problem by adding an implicit break after every arm. Multiple conditions can be comma-separated to execute the same block of code. There’s no way to achieve the same result as 3 and 4. This feels natural because the body of a match arm can only contain a single expression.

echo match ($x) {
    1, 2 => 'Same for 1 and 2',
    3, 4 => 'Same for 3 and 4',
};

Exhaustiveness

Another large source of bugs is not handling all the possible cases supplied to the switch statement.

switch ($configuration) {
    case Config::FOO:
        // ...
        break;
    case Config::BAR:
        // ...
        break;
}

This will go unnoticed until the program crashes in a weird way, causes strange behavior or even worse becomes a security hole. Many languages can check if all the cases are handled at compile time or force you to write a default case if they can’t. For a dynamic language like PHP the only alternative is throwing an error. This is exactly what the match expression does. It throws an UnhandledMatchError if the condition isn’t met for any of the arms.

$y = match ($x) {
    1 => 'Foo',
    2 => 'Bar',
};
 
// Only reached if $x is 1 or 2
// $y is 'Foo' or 'Bar'

Miscellaneous

Arbitrary expressions

A match condition can be any arbitrary expression. Analogous to switch each condition will be checked from top to bottom until the first one matches. If a condition matches the remaining conditions won’t be evaluated.

$y = match ($x) {
    foo() => ...,
    $this->bar() => ..., // bar() isn't called if foo() matched with $x
    $this->baz => ...,
    // etc.
};

Future scope

Blocks

In this RFC the body of a match arm must be an expression. Blocks for match and arrow functions will be discussed in a separate RFC.

Pattern matching

I have experimented with pattern matching and decided not to include it in this RFC. Pattern matching is a complex topic and requires a lot of thought. Each pattern should be discussed in detail in a separate RFC.

Allow dropping (true)

$x = match { ... };
// Equivalent to
$x = match (true) { ... };

Backward Incompatible Changes

match was added as a keyword (reserved_non_modifiers). This means it can’t be used in the following contexts anymore:

  • namespaces
  • class names
  • function names
  • global constants

Note that it will continue to work in method names and class constants.

Vote

Voting started 2020-xx-xx and closes 2020-xx-xx.

rfc/match_expression_v2.1590066416.txt.gz · Last modified: 2020/05/21 13:06 by ilijatovilo