rfc:match_expression_v2

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
rfc:match_expression_v2 [2020/05/21 13:06] – Convert with pandoc instead ilijatovilorfc:match_expression_v2 [2020/07/09 22:06] (current) – Move to implemented ilutov
Line 1: Line 1:
 ====== PHP RFC: Match expression v2 ====== ====== PHP RFC: Match expression v2 ======
  
-  * Date: 2020-05-16+  * Date: 2020-05-22
   * Author: Ilija Tovilo, tovilo.ilija@gmail.com   * Author: Ilija Tovilo, tovilo.ilija@gmail.com
-  * Status: Under discussion+  * Status: Implemented
   * Target Version: PHP 8.0   * Target Version: PHP 8.0
   * Implementation: https://github.com/php/php-src/pull/5371   * Implementation: https://github.com/php/php-src/pull/5371
Line 11: Line 11:
  
 This RFC proposes adding a new ''%%match%%'' expression that is similar to ''%%switch%%'' but with safer semantics and the ability to return values. This RFC proposes adding a new ''%%match%%'' expression that is similar to ''%%switch%%'' but with safer semantics and the ability to return values.
 +
 +[[https://github.com/doctrine/orm/blob/72bc09926df1ff71697f4cc2e478cf52f0aa30d8/lib/Doctrine/ORM/Query/Parser.php#L816|From the Doctrine query parser]]:
  
 <code php> <code php>
-$expressionResult = match ($condition) { +// Before 
-    1, 2 => foo(), +switch ($this->lexer->lookahead['type']) { 
-    3=> bar(), +    case Lexer::T_SELECT: 
-    default => baz(),+        $statement = $this->SelectStatement(); 
 +        break; 
 + 
 +    case Lexer::T_UPDATE: 
 +        $statement = $this->UpdateStatement(); 
 +        break; 
 + 
 +    case Lexer::T_DELETE: 
 +        $statement = $this->DeleteStatement(); 
 +        break; 
 + 
 +    default: 
 +        $this->syntaxError('SELECT, UPDATE or DELETE'); 
 +        break; 
 +
 + 
 +// After 
 +$statement = match ($this->lexer->lookahead['type']) { 
 +    Lexer::T_SELECT => $this->SelectStatement(), 
 +    Lexer::T_UPDATE => $this->UpdateStatement(), 
 +    Lexer::T_DELETE => $this->DeleteStatement(), 
 +    default => $this->syntaxError('SELECT, UPDATE or DELETE'),
 }; };
 </code> </code>
Line 28: Line 51:
 switch (1) { switch (1) {
     case 0:     case 0:
-        $= 'Foo';+        $result = 'Foo';
         break;         break;
     case 1:     case 1:
-        $= 'Bar';+        $result = 'Bar';
         break;         break;
     case 2:     case 2:
-        $= 'Baz';+        $result = 'Baz';
         break;         break;
 } }
  
-echo $y;+echo $result;
 //> Bar //> Bar
 </code> </code>
-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.+It is easy to forget assigning ''%%$result%%'' in one of the cases. It is also visually unintuitive to find ''%%$result%%'' 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.
  
 <code php> <code php>
Line 58: Line 81:
 switch ('foo') { switch ('foo') {
     case 0:     case 0:
-      echo "Oh no!\n";+      $result = "Oh no!\n"; 
 +      break; 
 +    case 'foo': 
 +      $result = "This is what I expected\n";
       break;       break;
 } }
 +echo $result;
 +//> Oh no!
 </code> </code>
 The ''%%match%%'' expression uses strict comparison (''%%===%%'') instead. The comparison is strict regardless of ''%%strict_types%%''. The ''%%match%%'' expression uses strict comparison (''%%===%%'') instead. The comparison is strict regardless of ''%%strict_types%%''.
Line 66: Line 94:
 <code php> <code php>
 echo match ('foo') { echo match ('foo') {
-    0 => "Never reached\n",+    0 => "Oh no!\n", 
 +    'foo' => "This is what I expected\n",
 }; };
 +//> This is what I expected
 </code> </code>
 ==== No fallthrough ==== ==== No fallthrough ====
Line 83: Line 113:
 } }
 </code> </code>
-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.+The ''%%match%%'' expression resolves this problem by adding an implicit ''%%break%%'' after every arm.
  
 <code php> <code php>
-switch ($x) { +match ($pressedKey) { 
-    case 1: +    Key::RETURN_ => save(), 
-    case 2: +    Key::DELETE => delete(), 
-        // Same for 1 and 2 +};
-        break; +
-    case 3: +
-        // Only 3 +
-    case 4+
-        // Same for 3 and 4 +
-        break; +
-}+
 </code> </code>
-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.+Multiple conditions can be comma-separated to execute the same block of code.
  
 <code php> <code php>
Line 111: Line 134:
  
 <code php> <code php>
-switch ($configuration) { +switch ($operator) { 
-    case Config::FOO+    case BinaryOperator::ADD
-        // ... +        $result = $lhs + $rhs;
-        break; +
-    case Config::BAR: +
-        // ...+
         break;         break;
 } }
 +
 +// Forgot to handle BinaryOperator::SUBTRACT
 </code> </code>
-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.+This will go unnoticed until the program crashes in a weird way, causes strange behavior or even worse becomes a security hole. ''%%match%%'' throws an ''%%UnhandledMatchError%%'' if the condition isn’t met for any of the arms. This allows mistakes to be caught early on.
  
 <code php> <code php>
-$= match ($x) { +$result = match ($operator) { 
-    => 'Foo', +    BinaryOperator::ADD => $lhs + $rhs,
-    2 => 'Bar',+
 }; };
  
-// Only reached if $is 1 or 2 +// Throws when $operator is BinaryOperator::SUBTRACT
-// $y is 'Foo' or 'Bar'+
 </code> </code>
 ===== Miscellaneous ===== ===== Miscellaneous =====
Line 138: Line 158:
  
 <code php> <code php>
-$= match ($x) {+$result = match ($x) {
     foo() => ...,     foo() => ...,
     $this->bar() => ..., // bar() isn't called if foo() matched with $x     $this->bar() => ..., // bar() isn't called if foo() matched with $x
Line 158: Line 178:
  
 <code php> <code php>
-$= match { ... };+$result = match { ... };
 // Equivalent to // Equivalent to
-$= match (true) { ... };+$result = match (true) { ... };
 </code> </code>
 ===== Backward Incompatible Changes ===== ===== Backward Incompatible Changes =====
Line 172: Line 192:
  
 Note that it will continue to work in method names and class constants. Note that it will continue to work in method names and class constants.
 +
 +===== Syntax comparison =====
 +https://gist.github.com/iluuu1994/11ac292cf7daca8162798d08db219cd5
  
 ===== Vote ===== ===== Vote =====
  
-Voting started 2020-xx-xx and closes 2020-xx-xx.+Voting starts 2020-06-19 and ends 2020-07-03 
 + 
 +As this is a language change, a 2/3 majority is required. 
 + 
 +<doodle title="Add match expressions to the language?" auth="ilutov" voteType="single" closed="true"> 
 +   * Yes 
 +   * No 
 +</doodle>
rfc/match_expression_v2.1590066416.txt.gz · Last modified: 2020/05/21 13:06 by ilijatovilo