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/20 17:44] – Allow returning values from blocks ilijatovilorfc:match_expression [2020/04/25 10:03] – Adjust adt syntax 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 172: Line 172:
 </code> </code>
  
-When you're making use of the result value of the ''match'' expression you must return a value from each block to the ''match'' expression. This can be done by omitting the last semicolon in the block. This syntax is borrowed (pun intended) from Rust ((https://doc.rust-lang.org/reference/expressions/block-expr.html)).+Originally this RFC included a way to return a value from 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 a part of this proposal and will be discussed in a separate RFC.
  
 <code php> <code php>
-match ($x) {+// Original proposal 
 +$y = match ($x) {
     0 => {     0 => {
         foo();         foo();
Line 181: Line 182:
         baz() // This value is returned         baz() // This value is returned
     },     },
-}+}
 + 
 +// 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> </code>
  
-This is **not** the same as using the ''return'' keyword.+For the time being using blocks in match expressions that use the return value in any way results in a compilation error:
  
 <code php> <code php>
-function test() { +$y = match ($x) { 
-    $y = match ($x) { +    0 => {}, 
-        0 => { +}; 
-            foo(); +//> Match that is not used as a statement can't contain blocks 
-            bar(); + 
-            // baz() will be returned from test(), $y will never be assigned +foo(match ($x{ 
-            return baz(); +    0 => {}, 
-        }, +}); 
-    };+//> Match that is not used as a statement can't contain blocks 
 + 
 +1 + match ($x{ 
 +    0 => {}, 
 +}; 
 +//> Match that is not used as a statement can't contain blocks 
 + 
 +//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 214: 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 222: 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 238: 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. A potential gotcha is passing truthy values to the match which will not work as intended. But of course this issue remains regardless of dropping ''(true)'' or not. 
 + 
 +<code php> 
 +match { 
 +    preg_match(...) => {}, // preg_match returns 1 which is NOT identical (===) to true 
 +
 +</code> 
 + 
 +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 266: Line 372:
 </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 282: Line 419:
  
 ===== 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 289: Line 429:
 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 294: Line 435:
     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 312: Line 457:
 </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 336: Line 506:
 </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 357: Line 527:
  
 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