rfc:match_expression

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
Next revisionBoth sides next revision
rfc:match_expression [2020/04/19 23:05] – Move pattern matching to future scope ilijatovilorfc:match_expression [2020/04/25 09:46] – Rename prefix op to unary ilijatovilo
Line 89: Line 89:
  
 ==== Fallthrough ==== ==== Fallthrough ====
-The ''switch'' fallthrough has been a large source of bugs in many languages. Each ''case'' must explicitely ''break'' out of the ''switch'' statement or the execution will continue into the next ''case'' even if the condition is not met.+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.
  
 <code php> <code php>
 switch ($pressedKey) { switch ($pressedKey) {
-    case Key::ENTER:+    case Key::RETURN_:
         save();         save();
         // Oops, forgot the break         // Oops, forgot the break
Line 160: Line 160:
  
 ===== Blocks ===== ===== Blocks =====
-Sometimes passing a single expression to a match arm isn't enough, either because you need to use a statement or the code is just too long for a single expression. In those cases you can pass a block to the arm that can contain a list of statements.+Sometimes passing a single expression to a match arm isn't enough, either because you need to use a statement or the code is just too long for a single expression. In those cases you can pass a block to the arm.
  
 <code php> <code php>
Line 172: Line 172:
 </code> </code>
  
-It is not possible to return a value from the block. Thus blocks are only allowed when using the ''match'' expression as statement. The following code will lead to compilation error.+Originally this RFC included a way to return a value from a block by omitting the semicolon of the last expression. This syntax is borrowed from Rust ((https://doc.rust-lang.org/reference/expressions/block-expr.html)). Due to [[https://github.com/php/php-src/pull/5448|memory management difficulties]] and a lot of negative feedback on the syntax this is no longer part of this proposal and will be discussed in separate RFC.
  
 <code php> <code php>
-$= match ($x) { +// Original proposal 
-    0 => { /* How do I return a value? */ },+$= match ($x) { 
 +    0 => { 
 +        foo(); 
 +        bar(); 
 +        baz() // This value is returned 
 +    },
 }; };
-//> Fatal error: Match expressions that are not statements can't contain statement lists 
  
-// The same goes for+// Alternative syntax, <= 
 +$y = match ($x) { 
 +    0 => { 
 +        foo(); 
 +        bar(); 
 +        <= baz(); 
 +    }, 
 +}; 
 + 
 +// Alternative syntax, separate keyword 
 +$y = match ($x) { 
 +    0 => { 
 +        foo(); 
 +        bar(); 
 +        pass baz(); 
 +    }, 
 +}; 
 + 
 +// Alternative syntax, automatically return last expression regardless of semicolon 
 +$y = match ($x) { 
 +    0 => { 
 +        foo(); 
 +        bar(); 
 +        baz(); 
 +    }, 
 +}; 
 +</code> 
 + 
 +For the time being using blocks in match expressions that use the return value in any way results in a compilation error: 
 + 
 +<code php> 
 +$y = match ($x) { 
 +    0 => {}, 
 +}; 
 +//> Match that is not used as a statement can't contain blocks
  
 foo(match ($x) { foo(match ($x) {
-    0 => { ... },+    0 => {},
 }); });
 +//> Match that is not used as a statement can't contain blocks
  
-echo 1 + match ($x) { +1 + match ($x) { 
-    0 => { ... },+    0 => {},
 }; };
 +//> Match that is not used as a statement can't contain blocks
  
-// etc.+//etc. 
 + 
 +// Only allowed form 
 +match ($x) { 
 +    0 => {}, 
 +}
 </code> </code>
  
-===== Semicolon =====+===== Optional semicolon for match in statement form =====
 When using ''match'' as part of some other expression it is necessary to terminate the statement with a semicolon. When using ''match'' as part of some other expression it is necessary to terminate the statement with a semicolon.
  
Line 208: Line 253:
 </code> </code>
  
-However, to make the ''match'' expression more similar to other statements like ''if'' and ''switch'' it is allowed to drop the semicolon in this case only.+However, to make the ''match'' expression more similar to other statements like ''if'' and ''switch'' we could allow dropping the semicolon in this case only.
  
 <code php> <code php>
Line 216: Line 261:
 </code> </code>
  
-This introduces some ambiguities with prefix operators that are also binary operators, namely ''+'' and ''-''.+This introduces an ambiguity with the ''+'' and ''-'' unary operators.
  
 <code php> <code php>
Line 232: Line 277:
 </code> </code>
  
-When ''match'' appears as the first element of a statement it will always be parsed as option 1.+''match'' that appears as the first element of a statement would always be parsed as option 1 because there are no legitimate use cases for binary operations at a statement level. All other cases work as expected.
  
-===== break/continue =====+<code php> 
 +// These work fine 
 +$x match ($y) { ... } - 1; 
 +foo(match ($y) { ... } - 1); 
 +$x[] = fn($y) => match ($y) { ... }; 
 +// etc. 
 +</code> 
 + 
 +This is also how Rust solves this ambiguity((https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=31b5bca165323726e7aa8d14f62f94d5)). 
 + 
 +<code rust> 
 +match true { _ => () } - 1; 
 + 
 +// warning: unused unary operation that must be used 
 +//  --> src/main.rs:2:28 
 +//   | 
 +// 2 |     match true { _ => () } - 1; 
 +//                              ^^^ 
 +//   | 
 +</code> 
 + 
 +Because there was some controversy around this feature it was moved to a secondary vote. 
 + 
 +===== Allow dropping (true) condition ===== 
 +It has been suggested to make no condition equivalent to ''(true)''
 + 
 +<code php> 
 +match { 
 +    $age >= 30 => {}, 
 +    $age >= 20 => {}, 
 +    $age >= 10 => {}, 
 +    default => {}, 
 +
 + 
 +// Equivalent to 
 + 
 +match (true) { 
 +    $age >= 30 => {}, 
 +    $age >= 20 => {}, 
 +    $age >= 10 => {}, 
 +    default => {}, 
 +
 +</code> 
 + 
 +The keyword ''match'' could be a bit misleading here. Because I have no strong opinion on this it will be moved to a secondary vote. 
 + 
 +===== 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 
 + 
 +<code php> 
 +match ($x) { 
 +    foo() => ..., 
 +    $this->bar() => ..., // bar() isn't called if foo() matched with $x 
 +    $this->baz => ..., 
 +    // etc. 
 +
 +</code> 
 + 
 +==== break/continue ====
 Just like with the switch you can use ''break'' to break out of the executed arm. Just like with the switch you can use ''break'' to break out of the executed arm.
  
Line 260: Line 364:
 </code> </code>
  
-===== return =====+==== goto ==== 
 +Like with the ''switch'' you can use ''goto'' to jump out of ''match'' expressions. 
 + 
 +<code php> 
 +match ($a) { 
 +    1 => { 
 +        match ($b) { 
 +            2 => { 
 +                goto end_of_match; 
 +            }, 
 +        } 
 +    }, 
 +
 + 
 +end_of_match: 
 +</code> 
 + 
 +It is not allowed to jump into match expressions. 
 + 
 +<code php> 
 +goto match_arm; 
 + 
 +match ($b) { 
 +    1 => { 
 +        match_arm: 
 +    }, 
 +
 + 
 +//> Fatal error: 'goto' into loop, switch or match is disallowed 
 +</code> 
 + 
 +==== return ====
 ''return'' behaves the same as in any other context. It will return from the function. ''return'' behaves the same as in any other context. It will return from the function.
  
Line 276: Line 411:
  
 ===== Future scope ===== ===== Future scope =====
 +==== Block expressions ====
 +As mentioned above block expressions will be discussed in a separate RFC. We'll also use this opportunity to think about blocks in arrow functions.
 +
 ==== Pattern matching ==== ==== Pattern matching ====
-I have experimented with pattern matching ((https://github.com/php/php-src/compare/master...iluuu1994:pattern-matching)) for this RFC. Realistically it could sometimes save few keystrokesIn my opinion this does not justify the significant complexity added to the langage at the moment. It would be mostly useful for algebraic data types which PHP currently does not have.+I have experimented with pattern matching ((https://github.com/php/php-src/compare/master...iluuu1994:pattern-matching)) and decided not to include it in this RFC. Pattern matching is complex topic and requires a lot of thoughtEach pattern should be discussed in detail in a separate RFC.
  
 <code php> <code php>
Line 283: Line 421:
 match ($value) { match ($value) {
     let $a => ..., // Identifer pattern     let $a => ..., // Identifer pattern
 +    let 'foo' => ..., // Scalar pattern
     let 0..<10 => ..., // Range pattern     let 0..<10 => ..., // Range pattern
     let is string => ..., // Type pattern     let is string => ..., // Type pattern
Line 288: Line 427:
     let Foo { foo: 1, getBar(): 2 } => ..., // Object pattern     let Foo { foo: 1, getBar(): 2 } => ..., // Object pattern
     let $str @ is string if $str !== '' => ..., // Guard     let $str @ is string if $str !== '' => ..., // Guard
 +
 +    // Algebraic data types if we ever get them
 +    let Ast\BinaryExpr($lhs, '+', $rhs) => ...,
 } }
  
 // Without pattern matching // Without pattern matching
 match (true) { match (true) {
-    true => $value ..., // Identifer pattern+    true => $value ..., // Identifier pattern 
 +    'foo' => ..., // Scalar pattern
     $value >= 0 && $value < 10 => ..., // Range pattern     $value >= 0 && $value < 10 => ..., // Range pattern
     is_string($value) => ..., // Type pattern     is_string($value) => ..., // Type pattern
Line 306: Line 449:
 </code> </code>
  
-While some patterns are significantly shorter (namely the array pattern) code like that is relatively rareAt the moment the arugment for such a big language change is pretty weakIf the situation ever changes we can always add pattern matching at a later point in time.+==== Explicit fallthrough ==== 
 +Some people have suggested allowing explicit fallthrough to the next armThis is, however, not a part of this RFC. 
 + 
 +<code php> 
 +match ($x) { 
 +    1 => { 
 +        foo(); 
 +        fallthrough; 
 +    }, 
 +    2 => { 
 +        bar(); 
 +    }, 
 +
 + 
 +// 1 calls foo() and bar() 
 +// 2 only calls bar() 
 +</code> 
 + 
 +This would require a few sanity checks with pattern matching. 
 + 
 +<code php> 
 +match ($x) { 
 +    $a => { fallthrough; }, 
 +    $b => { /* $b is undefined */ }, 
 +
 +</code>
  
 ===== "Why don't you just use x" ===== ===== "Why don't you just use x" =====
Line 330: Line 498:
 </code> </code>
  
-This code will execute every single "arm", not just the one that is finally returned. It will also build a hash map in memory everytime it is executed. And again, you must not forget to handle unexpected values.+This code will execute every single "arm", not just the one that is finally returned. It will also build a hash map in memory every time it is executed. And again, you must not forget to handle unexpected values.
  
 ==== Nested ternary operators ==== ==== Nested ternary operators ====
Line 351: Line 519:
  
 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.
- 
-===== Proposed PHP Version(s) ===== 
-The proposed version is PHP 8. 
  
 ===== Proposed Voting Choices ===== ===== Proposed Voting Choices =====
-As this is a language change, a 2/3 majority is required. The vote is a straight Yes/No vote for accepting the RFC and merging the patch.+As this is a language change, a 2/3 majority is required. 
 + 
 +<doodle title="Would you like to add match expressions to the language?" auth="ilijatovilo" voteType="single" closed="true"> 
 +   * Yes 
 +   * No 
 +</doodle> 
 + 
 +Secondary vote (choice with the most votes is picked): 
 + 
 +<doodle title="Should the semicolon for match in statement form be optional?" auth="ilijatovilo" voteType="single" closed="true"> 
 +   Yes 
 +   No 
 +</doodle> 
 + 
 +Secondary vote (choice with the most votes is picked): 
 + 
 +<doodle title="Should we allow dropping (true) condition?" auth="ilijatovilo" voteType="single" closed="true"> 
 +   * Yes 
 +   * No 
 +</doodle> 
 + 
 +==== If you voted no, why? ==== 
 + 
 +  - Not interested 
 +  - I don't want blocks 
 +  - Missing return values in blocks 
 +  - Missing pattern matching 
 +  - Missing explicit fallthrough 
 +  - BC break is not acceptable 
 +  - Wanted [[https://wiki.php.net/rfc/switch_expression|switch expressions]] instead 
 +  - Other 
 + 
 +<doodle title="If you voted no, why?" auth="ilijatovilo" voteType="multi" closed="true"> 
 +   * 1 
 +   * 2 
 +   * 3 
 +   * 4 
 +   * 5 
 +   * 6 
 +   * 7 
 +   * 8 
 +</doodle>
  
rfc/match_expression.txt · Last modified: 2020/05/09 15:59 by ilijatovilo