rfc:match_expression

Differences

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

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
rfc:match_expression [2020/04/24 00:05]
tandre fix nit on undefined variable in an example
rfc:match_expression [2020/05/09 15:59] (current)
ilijatovilo Move to declined
Line 2: Line 2:
   * Date: 2020-04-12   * Date: 2020-04-12
   * Author: Ilija Tovilo, tovilo.ilija@gmail.com   * Author: Ilija Tovilo, tovilo.ilija@gmail.com
-  * Status: Under Discussion+  * Status: Declined
   * 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 93: Line 93:
 <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>
 +// Original proposal
 $y = match ($x) { $y = match ($x) {
     0 => {     0 => {
Line 182: Line 183:
     },     },
 }; };
-</code> 
  
-Not returning a value from a block will lead to a compilation error.+// Alternative syntax, <= 
 +$y = match ($x) { 
 +    0 => { 
 +        foo(); 
 +        bar(); 
 +        <= baz(); 
 +    }, 
 +};
  
-<code php>+// Alternative syntax, separate keyword
 $y = match ($x) { $y = match ($x) {
     0 => {     0 => {
         foo();         foo();
         bar();         bar();
-        baz();+        pass baz();
     },     },
 }; };
  
-//> Match block must return a value. Did you mean to omit the last semicolon?+// 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($x) { +$y = match ($x) { 
-    $y = match ($x) { +    => {}, 
-        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 228: 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 236: 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 252: 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. 
 + 
 +<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 ===== ===== Miscellaneous =====
 ==== Arbitrary expressions ==== ==== 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.+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> <code php>
 match ($x) { match ($x) {
-    functionCall() => ..., +    foo() => ..., 
-    $this->methodCall() => ..., +    $this->bar() => ..., // bar() isn't called if foo() matched with $x 
-    $this->property => ...,+    $this->baz => ...,
     // etc.     // etc.
 } }
Line 291: Line 370:
 } }
 //> Fatal error: "continue" targeting match is disallowed. Did you mean to use "break" or "continue 2"? //> Fatal error: "continue" targeting match is disallowed. Did you mean to use "break" or "continue 2"?
-</code> 
- 
-It is not allowed to break out of a match expression that makes use of the return value. 
- 
-<code php> 
-$x = match ($i) { 
-    default => { 
-        break; 
-        'foo' 
-    }, 
-}; 
- 
-echo $x; 
- 
-//> Fatal error: Breaking out of match with result value disallowed 
 </code> </code>
  
Line 337: Line 401:
  
 //> Fatal error: 'goto' into loop, switch or match is disallowed //> Fatal error: 'goto' into loop, switch or match is disallowed
-</code> 
- 
-Nor is it allowed to jump out of ''match'' expressions that make use of the return value. 
- 
-<code php> 
-$a = match ($b) { 
-    1 => { 
-        goto end_of_match; 
-        'foo' 
-    }, 
-}; 
- 
-end_of_match: 
- 
-//> Fatal error: 'goto' out of match with result value disallowed 
 </code> </code>
  
Line 370: 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 language 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 377: 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 382: 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) => ...,
 } }
  
Line 387: Line 443:
 match (true) { match (true) {
     true => $value ..., // Identifier 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 400: Line 457:
 </code> </code>
  
-While some patterns are significantly shorter (namely the array pattern) code like that is relatively rare. At the moment the argument for such a big language change is pretty weak. If 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 arm. This is, however, not part of this RFC.
-==== Language wide blocks ==== +
-While it is possible to make blocks language wide feature there are simply not enough use cases for this at the moment. One potential use case are arrow functions.+
  
 <code php> <code php>
-$callable = fn() => { +match ($x
-    return 'foo'+    1 => { 
-};+        foo()
 +        fallthrough; 
 +    }
 +    2 => { 
 +        bar(); 
 +    }, 
 +
 + 
 +// 1 calls foo() and bar() 
 +// 2 only calls bar()
 </code> </code>
  
-However, allowing the same blocks here would allow for two semantically equivalent versions with different syntax.+This would require a few sanity checks with pattern matching.
  
 <code php> <code php>
-$callable = fn() => { +match ($x
-    'foo' +    $a => { fallthrough; }, 
-};+    $b => { /* $b is undefined */ }, 
 +}
 </code> </code>
- 
-We would likely want to disallow this. So even here blocks should behave slightly differently. Thus we're better off implementing blocks for arrow functions separately. 
- 
-There were no other legitimate use cases I could think of. 
  
 ===== "Why don't you just use x" ===== ===== "Why don't you just use x" =====
Line 445: 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 466: 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.+Voting starts 2020-04-25 and ends 2020-05-09.  
 + 
 +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.1587686714.txt.gz · Last modified: 2020/04/24 00:05 by tandre