[RFC] switch expression

php.internals

Ilija Tovilo

6 years ago
Hi everybody! A few years ago I suggested adding a new `match` expression to the PHP language: https://externals.io/message/100487 I arrogantly assumed someone will implement it for me which of course didn't happen. I'd finally like to get my own hands dirty. I have a very rough, incomplete prototype but I'd like to get your feedback before I continue working on the details. # Introduction This is what it looks like: ```php echo $i switch {     0 => "i equals 0",     1 => "i equals 1",     2 => "i equals 2",     3, 4 => "i equals 3 or 4", }; // is roughly equivalent to switch ($i) {     case 0:         $tmp = "i equals 0";         break;     case 1:         $tmp = "i equals 1";         break;     case 2:         $tmp = "i equals 2";         break;     case 3:     case 4:         $tmp = "i equals 3 or 4";         break;     default:         throw new InvalidArgumentException('Unhandled switch case'); } echo $tmp; ``` Some things to note: * Each case only accepts a single expression * The entire switch expression evaluates to the result of the executed case * There is no fallthrough, an implicit break is added after every case * Multiple case conditions are possible with comma separation * The default case throws a InvalidArgumentException by default * The switch keyword is used as an infix operator # Syntax Originally, I expected to reuse the current syntax and transform it into an expression. ```php $x = switch ($y) { ... }; ``` Turns out this is ambiguous. ```php switch ($y) { ... } [$a] = ...; // Could also be interpreted as switch ($y) { ... }[$a] = ...; ``` I stole the new syntax from C# 8.0 which means at least some people will already be familiar with it: https://docs.microsoft.com/en-us/archive/msdn-magazine/2019/may/csharp-8-0-pattern-matching-in-csharp-8-0#the-evolution-of-pattern-matching-in-c-80 # Type coercion One of the bigger weak points of the `switch` statement is the fact that it performs implicit type coercion. ```php switch ('foo') {     case 0:         echo "Oh no!\n"; } ``` While it's very tempting to fix this in the new `switch` expression it adds a confusing discrepancy between the `switch` statement and expression. I think it would be preferrable to keep the two the same and change the behavior of both in a new PHP edition (https://github.com/php/php-rfcs/pull/2). # Pattern matching I decided against pattern matching because PHP doesn't have algebraic data types and classes rarely have public properties. In my opinion the limited use cases don't justify the significant complexity added to the language. It would also, once again, add an unjustified discrepancy between the `switch` statement and expression. If at some point we do want to introduce pattern matching it might be better to introduce a different keyword (e.g. `match`) and make it work for both the statement and expression. In case you need to match a more complex expression the following still works fine: ```php echo true switch {     is_int($x) => 'int',     is_float($x) => 'float',     is_string($x) => 'string',     ... }; ``` # Blocks Sometimes it would be useful to split the expression into multiple statements to make it more readable. Unfortunately, in PHP there are no block expressions. Rust allows returning the last value by omitting the semicolon: ```php echo $x switch {     1 => {         foo();         bar();         baz()     }, }; ``` This is indeed possible in PHP and could be implemented as part of the `switch` expression or as a general language feature. A nice side effect is that this could also be used in arrow functions: ```php $x = fn() => {         foo();         bar();         baz() }; ``` This would, however, make it inconsistent with closures as they use the return keyword. Thus we would probably have to make sure arrow functions still work with return statement which would decrease the need for such a language construct. It is also very unlike anything else in PHP. # Poll This is a short overview of what I'll be working on in the coming weeks. I created a short poll for you guys to let me know if this idea is worth pursuing: https://forms.gle/stXMv72CAaDDxfwf8 Stay safe!

Michał Brzuchalski

6 years ago
Hi Ilija, śr., 25 mar 2020 o 13:10 Ilija Tovilo <ilija.tovilo@me.com> napisał(a):
> Hi everybody! > > > > A few years ago I suggested adding a new `match` expression to the PHP > language: > > https://externals.io/message/100487 > > > > I arrogantly assumed someone will implement it for me which of course > didn't happen. I'd finally like to get my own hands dirty. I have a very > rough, incomplete prototype but I'd like to get your feedback before I > continue working on the details. > > > > # Introduction > > > > This is what it looks like: > > > > ```php > > echo $i switch { > > 0 => "i equals 0", > > 1 => "i equals 1", > > 2 => "i equals 2", > > 3, 4 => "i equals 3 or 4", > > }; > > > > // is roughly equivalent to > > > > switch ($i) { > > case 0: > > $tmp = "i equals 0"; > > break; > > case 1: > > $tmp = "i equals 1"; > > break; > > case 2: > > $tmp = "i equals 2"; > > break; > > case 3: > > case 4: > > $tmp = "i equals 3 or 4"; > > break; > > default: > > throw new InvalidArgumentException('Unhandled switch case'); > > } > > > > echo $tmp; > > ``` > > > > Some things to note: > > > > * Each case only accepts a single expression > > * The entire switch expression evaluates to the result of the executed case > > * There is no fallthrough, an implicit break is added after every case > > * Multiple case conditions are possible with comma separation > > * The default case throws a InvalidArgumentException by default > > * The switch keyword is used as an infix operator > > > > # Syntax > > > > Originally, I expected to reuse the current syntax and transform it into > an expression. > > > > ```php > > $x = switch ($y) { ... }; > > ``` > > > > Turns out this is ambiguous. > > > > ```php > > switch ($y) { ... } > > [$a] = ...; > > > > // Could also be interpreted as > > switch ($y) { ... }[$a] = ...; > > ``` > > > > I stole the new syntax from C# 8.0 which means at least some people will > already be familiar with it: > > > https://docs.microsoft.com/en-us/archive/msdn-magazine/2019/may/csharp-8-0-pattern-matching-in-csharp-8-0#the-evolution-of-pattern-matching-in-c-80 > > > > # Type coercion > > > > One of the bigger weak points of the `switch` statement is the fact that > it performs implicit type coercion. > > > > ```php > > switch ('foo') { > > case 0: > > echo "Oh no!\n"; > > } > > ``` > > > > While it's very tempting to fix this in the new `switch` expression it > adds a confusing discrepancy between the `switch` statement and expression. > I think it would be preferrable to keep the two the same and change the > behavior of both in a new PHP edition ( > https://github.com/php/php-rfcs/pull/2). > > > > # Pattern matching > > > > I decided against pattern matching because PHP doesn't have algebraic data > types and classes rarely have public properties. In my opinion the limited > use cases don't justify the significant complexity added to the language. > It would also, once again, add an unjustified discrepancy between the > `switch` statement and expression. If at some point we do want to introduce > pattern matching it might be better to introduce a different keyword (e.g. > `match`) and make it work for both the statement and expression. In case > you need to match a more complex expression the following still works fine: > > > > ```php > > echo true switch { > > is_int($x) => 'int', > > is_float($x) => 'float', > > is_string($x) => 'string', > > ... > > }; > > ``` > > > > # Blocks > > > > Sometimes it would be useful to split the expression into multiple > statements to make it more readable. Unfortunately, in PHP there are no > block expressions. Rust allows returning the last value by omitting the > semicolon: > > > > ```php > > echo $x switch { > > 1 => { > > foo(); > > bar(); > > baz() > > }, > > }; > > ``` > > > > This is indeed possible in PHP and could be implemented as part of the > `switch` expression or as a general language feature. A nice side effect is > that this could also be used in arrow functions: > > > > ```php > > $x = fn() => { > > foo(); > > bar(); > > baz() > > }; > > ``` > > > > This would, however, make it inconsistent with closures as they use the > return keyword. Thus we would probably have to make sure arrow functions > still work with return statement which would decrease the need for such a > language construct. It is also very unlike anything else in PHP. > > > > # Poll > > > > This is a short overview of what I'll be working on in the coming weeks. I > created a short poll for you guys to let me know if this idea is worth > pursuing: > > https://forms.gle/stXMv72CAaDDxfwf8 > > > > Stay safe! > >
That looks like what I've described a few months ago in https://wiki.php.net/rfc/switch-expression-and-statement-improvement If you dig into the mailing list you can even find almost ready to use patch which implements it. I'd love switch expression inclusion in PHP. Cheers, Michał Brzuchalski

Ilija Tovilo

6 years ago
Hi Michał I’m sorry, unfortunately I missed your e-mail and RFC. Let me know if you’re still working on it and I’ll back off of course. Regards

Larry Garfield

6 years ago
On Wed, Mar 25, 2020, at 9:27 AM, Ilija Tovilo wrote:
> Hi Michał > > > > I’m sorry, unfortunately I missed your e-mail and RFC. > > Let me know if you’re still working on it and I’ll back off of course. > > > > Regards
I like the concept, and it looks like you're both on a similar track. Give or take details, I would very much like to see something like it. One possible improvement to either version is allowing an expression on the left side. That is, rather than doing an equality match, do a boolean match. That would then allow: $foo = switch($bar) { case $bar < 5 => $bar * 3; case $baz < 10 => $bar * 4; default => $bar * 5; }; That would sidestep the need for pattern matching, as you can do anything an expression can do. The obvious caveat of course is figuring out how to reference the variable being switched on, if it's not already a variable. My first thought there is to borrow the $$ variable name from Sara's old function composition proposal, but there may be others. There's likely other issues to discuss here but allowing expressions on the left would greatly improve the expressiveness of the construct. I'd prefer to not allow multi-line statements on the right, ie, blocks. That leads to too much potential for long and ugly code, which a construct like this should be avoiding. Limiting it to a single expression keeps it compact; if you have more involved logic, then put it in a function and your expression is just... calling that function. Problem solved. Side note: I did a limited user-space implementation of the same concept a while back: https://hive.blog/php/@crell/type-matching-in-php But I'd definitely rather see it in the native syntax. --Larry Garfield

Ilija Tovilo

6 years ago
Thanks for your feedback, Larry!
> One possible improvement to either version is allowing an expression on the left side. That is, rather than doing an equality match, do a boolean match.
This is how Rust does it: ```rust let x = match ... { Some(y) if y < 5 => ... } ``` In other words, you can add an additional guard to each case that excepts any expression. We don't really benefit a lot from that since we don't have pattern matching. I don't think this would add any significant benefit over: ```php $x = true switch { $x !== null && $x < 5 => ... } ``` Regards

Larry Garfield

6 years ago
On Wed, Mar 25, 2020, at 10:29 AM, Ilija Tovilo wrote:
> Thanks for your feedback, Larry! > > > One possible improvement to either version is allowing an expression on the left side. That is, rather than doing an equality match, do a boolean match. > > This is how Rust does it: > > ```rust > let x = match ... { > Some(y) if y < 5 => ... > } > ``` > > In other words, you can add an additional guard to each case that > excepts any expression. We don't really benefit a lot from that since > we don't have pattern matching. I don't think this would add any > significant benefit over: > > ```php > $x = true switch { > $x !== null && $x < 5 => ... > } > ```
Good point, I'd forgotten about that potential trick. So as long as an expression is allowed on the left, rather than just a literal, which is then == compared against the provided value, that should be "good enough" for most use cases. The implementation should include some tests to make sure that works properly, but I'm happy with the resulting syntax. So then the net result is: $var = switch($val) { case expr1 => expr2; } Where $val gets compared against the result of each expr1, and if true then $var is set to expr2. Endorse. --Larry Garfield

Dennis Birkholz

6 years ago
Hello together, Am 25.03.20 um 16:46 schrieb Larry Garfield:
> On Wed, Mar 25, 2020, at 10:29 AM, Ilija Tovilo wrote: >> Thanks for your feedback, Larry! >> >>> One possible improvement to either version is allowing an expression on the left side. That is, rather than doing an equality match, do a boolean match. >> >> This is how Rust does it: >> >> ```rust >> let x = match ... { >> Some(y) if y < 5 => ... >> } >> ``` >> >> In other words, you can add an additional guard to each case that >> excepts any expression. We don't really benefit a lot from that since >> we don't have pattern matching. I don't think this would add any >> significant benefit over: >> >> ```php >> $x = true switch { >> $x !== null && $x < 5 => ... >> } >> ``` > > Good point, I'd forgotten about that potential trick. So as long as an expression is allowed on the left, rather than just a literal, which is then == compared against the provided value, that should be "good enough" for most use cases. > > The implementation should include some tests to make sure that works properly, but I'm happy with the resulting syntax. > > So then the net result is: > > $var = switch($val) { > case expr1 => expr2; > } > > Where $val gets compared against the result of each expr1, and if true then $var is set to expr2.
on the first glance this all looks nice but you actually created something more like an if-expression that uses switch as a keyword because you stripped switch of some of its major features: - you compare the given value to possible cases -> you compare expressions to true - you can fall through to other cases without break - what about the default case? What about the following if-expression-syntax: $var = if ($x > 0) { return 1; } elseif ($x < 0) { return -1; } else { return 0; } Maybe this is more in line of what you want to do with your switch expression? Greets Dennis

Ilija Tovilo

6 years ago
Hi Dennis Thanks for your feedback!
> you can fall through to other cases without break
You could do the same using the || operator.
> what about the default case?
I haven't described the default case in my proposal but it is exactly what you'd expect it to be: ```php $var = true switch { $x > 0 => 1, $x < 0 => -1, default => 0, }; ```
> What about the following if-expression-syntax:
That would work (once again, Rust already does it) though not with the return keyword. We'd still need a block expression to pass the value from the block to the if expression. When I compare the two I definitely think the match expression is more readable. Regards

Christoph Becker

6 years ago
On 25.03.2020 at 17:06, Dennis Birkholz wrote:
> Hello together, > > Am 25.03.20 um 16:46 schrieb Larry Garfield: >> On Wed, Mar 25, 2020, at 10:29 AM, Ilija Tovilo wrote: >>> Thanks for your feedback, Larry! >>> >>>> One possible improvement to either version is allowing an expression on the left side. That is, rather than doing an equality match, do a boolean match. >>> >>> This is how Rust does it: >>> >>> ```rust >>> let x = match ... { >>> Some(y) if y < 5 => ... >>> } >>> ``` >>> >>> In other words, you can add an additional guard to each case that >>> excepts any expression. We don't really benefit a lot from that since >>> we don't have pattern matching. I don't think this would add any >>> significant benefit over: >>> >>> ```php >>> $x = true switch { >>> $x !== null && $x < 5 => ... >>> } >>> ``` >> >> Good point, I'd forgotten about that potential trick. So as long as an expression is allowed on the left, rather than just a literal, which is then == compared against the provided value, that should be "good enough" for most use cases. >> >> The implementation should include some tests to make sure that works properly, but I'm happy with the resulting syntax. >> >> So then the net result is: >> >> $var = switch($val) { >> case expr1 => expr2; >> } >> >> Where $val gets compared against the result of each expr1, and if true then $var is set to expr2. > > on the first glance this all looks nice but you actually created > something more like an if-expression that uses switch as a keyword > because you stripped switch of some of its major features: > - you compare the given value to possible cases -> you compare > expressions to true > - you can fall through to other cases without break > - what about the default case? > > What about the following if-expression-syntax: > > $var = if ($x > 0) { return 1; } > elseif ($x < 0) { return -1; } > else { return 0; } > > Maybe this is more in line of what you want to do with your switch > expression?
Or maybe even $var = $x > 0 ? 1 :($x < 0 ? -1 : 0); Yes, the required parentheses are ugly, but in my opinion, this is still better than a new if or switch(true) expression construct.
-- Christoph M. Becker

Rowan Collins

6 years ago
On Wed, 25 Mar 2020 at 15:29, Ilija Tovilo <ilija.tovilo@me.com> wrote:
> I don't think this would add any significant benefit over: > > ```php > $x = true switch { > $x !== null && $x < 5 => ... > } > ``` >
The problem with that is that it requires a temporary variable to be switched on. If I want to switch on, say, a method call, I can write this for equality: $result = $this->foo($bar) switch { 1 => 'hello', 2 => 'hi', 3 => 'goodbye' }; For inequalities, the switch(true) version looks something like this (the parentheses would probably be optional, but I'd personally use them for readability): $temp = $this->foo($bar); $result = true switch { ($temp <= 1) => 'hello', ($temp == 2) => 'hi', default => 'goodbye' } Using $$ to mean "value tested" would mean you could get rid of the temp variable, and just write this: $result = $this->foo($bar) switch { ($$ <= 1) => 'hello', ($$ == 2) => 'hi', default => 'goodbye' }; I've also previously thought about specifying an operator for switch statements, which could be used with expressions as well, e.g. $result = $this->foo($bar) switch <= { 1 => 'hello', 2 => 'hi', default => 'goodbye' }; Again, though, this is something that could be added to switch statements and expressions together, as a separate RFC. Regards,
-- Rowan Tommins [IMSoP]

Ilija Tovilo

6 years ago
Hi Rowan
> The problem with that is that it requires a temporary variable to be > switched on. If I want to switch on, say, a method call, I can write this > for equality:
I agree. The iffy part would be recognizing if the case expression should be equated to the switch input or evaluated on its own. That is still something we could address in a later RFC for both the statement and expression. Regards

Manuel Canga

6 years ago
Hi, internals, ---- En mié, 25 mar 2020 17:21:29 +0100 Rowan Tommins <rowan.collins@gmail.com> escribió ----
> On Wed, 25 Mar 2020 at 15:29, Ilija Tovilo <ilija.tovilo@me.com> wrote: > > > I don't think this would add any significant benefit over: > > > > ```php > > $x = true switch { > > $x !== null && $x < 5 => ... > > } > > ``` > > > > > The problem with that is that it requires a temporary variable to be > switched on. If I want to switch on, say, a method call, I can write this > for equality: > > $result = $this->foo($bar) switch { > 1 => 'hello', > 2 => 'hi', > 3 => 'goodbye' > };
In this case, you can also do: $result = [ 1 => 'hello', 2 => 'hi', 3 => 'goodbye' ][$this->foo($bar)]; With syntax very similar to proposed switch but this is using array. Regards
-- Manuel Canga

Gabriel O

6 years ago
This doesn't look like it can do fallbacks which are traditional feature of switch statements, can it? What I mean is this ``` case foo: case bar: return 1; case baz: return 2; ``` If we can't do that, I would suggest to go with different name, rather than reusing "switch".

Ilija Tovilo

6 years ago
Hi Gabriel
> This doesn't look like it can do fallbacks which are traditional feature of switch statements, can it?
You can take a look at the tests to get a feel for what it’s like: https://github.com/php/php-src/pull/5308/files Multiple conditions are possible: ``` return $day switch {     1, 7 => false,     2, 3, 4, 5, 6 => true, }; ``` Regards

Stas Malyshev

6 years ago
Hi!
> You can take a look at the tests to get a feel for what it’s like: > > https://github.com/php/php-src/pull/5308/files > > Multiple conditions are possible: > > ``` > > return $day switch { > >     1, 7 => false, > >     2, 3, 4, 5, 6 => true, > > };
I'm still not sure why if we're calling it "switch" it can't use traditional switch structure. If it uses something else, it must be called by some other keyword, it's not a good idea to have two "switch" constructs in the language with completely different syntax.
-- Stas Malyshev smalyshev@gmail.com

Ilija Tovilo

6 years ago
Hi Stanislav
> I'm still not sure why if we're calling it "switch"
C# does it. Not to say everything they do is right but it's reassuring that a big company like Microsoft had the same approach. https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/switch https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-8#switch-expressions In fact, the approach in the RFC is almost equivalent. Ilija

Stas Malyshev

6 years ago
Hi!
>> I'm still not sure why if we're calling it "switch" > > C# does it. Not to say everything they do is right but it's reassuring > that a big company like Microsoft had the same approach.
No it isn't. Having two syntaxes for one keyword is not a good idea, whether Microsoft is doing it or not. I understand why they did it, but it's still not a good idea.
-- Stas Malyshev smalyshev@gmail.com

Ilija Tovilo

6 years ago
> Having two syntaxes for one keyword is not a good idea,
We're already doing that. What about classes vs anonymous objects? Functions vs closures? They're using the same keywords. There's no confusion. Ilija

Girgias

6 years ago
On Sun, 29 Mar 2020 at 23:04, Ilija Tovilo <tovilo.ilija@gmail.com> wrote:
> > Having two syntaxes for one keyword is not a good idea, > > We're already doing that. What about classes vs anonymous objects? > Functions vs closures? > They're using the same keywords. There's no confusion. > > Ilija > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: http://www.php.net/unsub.php >
No offence but, that argument is dumb. A closure and a function are the same thing because the Closure class comes from a need to have consistent functions regardless of scope. And an anonymous object still behaves like a normal object, you can extend it, add methods to it, use traits. On the contrary having two different meaning for a fundamental construct is just weird, and if that goes to vote I'll vote against it. Moreover, no need to repeat mistakes other languages have made. Best regards George P. Banyard

Larry Garfield

6 years ago
On Sun, Mar 29, 2020, at 4:04 PM, Ilija Tovilo wrote:
> > Having two syntaxes for one keyword is not a good idea, > > We're already doing that. What about classes vs anonymous objects? > Functions vs closures? > They're using the same keywords. There's no confusion. > > Ilija
There's subtle and important differences there. An anonymous function is still a function, in that it is a routine that takes input and produces output. A closure is, technically, an anonymous function that has imported variables from its parent scope. Anonymous classes (not anonymous objects) are implemented as, legitimately, classes; they just have an arbitrary internal name. The object that results can be used exactly like any other object, by design. I don't believe that's the case here, however. `switch` is a language construct for a *statement*, which branches the flow of control of the program. What you're proposing is a language construct for an *expression*, which evaluates depending on internal logic to a different value. Those are sufficiently distinct that I agree they should have distinct keywords. Plus, the internal syntax is non-trivial to switch back and forth between (break vs not, etc.), so it is misleading for people to present them as two slight variants on the same thing; they're really quite distinct, and that's OK. My recommendation would be to just borrow Rust's keyword: $result = match ($var) { $expression => $expression; $expression => $expression; $expression => $expression; default => $expression; } --Larry Garfield

Stas Malyshev

6 years ago
Hi!
> My recommendation would be to just borrow Rust's keyword: > > $result = match ($var) { > $expression => $expression; > $expression => $expression; > $expression => $expression; > default => $expression; > }
Yes, this would be much better idea.
-- Stas Malyshev smalyshev@gmail.com

Girgias

6 years ago
On Sun, 29 Mar 2020 at 23:54, Stanislav Malyshev <smalyshev@gmail.com> wrote:
> Hi! > > > My recommendation would be to just borrow Rust's keyword: > > > > $result = match ($var) { > > $expression => $expression; > > $expression => $expression; > > $expression => $expression; > > default => $expression; > > } > > Yes, this would be much better idea. > > -- > Stas Malyshev > smalyshev@gmail.com >
Agreed George P. Banyard

Ilija Tovilo

6 years ago
Hi Larry Thanks for your suggestion. I chose to use switch instead of match for a couple of reasons: 1. It uses the same AST, code generation and opcodes as the switch, I don't agree that it is significantly different than the switch that we already have. 2. Adding the `match` keyword is a breaking change, not an insignificant one. 3. If we can fix the switch statement semantics in the future the two will only differ in that one returns a value and the other one doesn't. This is a much smaller distinction than functions/closure. 4. If we'd every want to add pattern matching, we'd still have a keyword available to us.
> I don't believe that's the case here, however. `switch` is a language construct for a *statement*, which branches the flow of control of the program. > > What you're proposing is a language construct for an *expression*, which evaluates depending on internal logic to a different value.
Well, I encourage you to look at the implementation. You'll see that the two are in fact very similar. I'm not averse to using the match keyword if people are ok with the BC change. If we do though we definitely should make it work as a statement, allow blocks in addition to single expression and also fix type coercion. This would make it a replacement of the switch statement instead of an addition. Ilija

Stas Malyshev

6 years ago
Hi!
> 1. It uses the same AST, code generation and opcodes as the switch, I > don't agree that it is significantly different than the switch that we > already have.
This is reasoning in wrong direction. Nobody cares which opcode it uses. It is significantly different *for the user*, the fact that it may compile into same opcode means nothing to the user.
> 2. Adding the `match` keyword is a breaking change, not an insignificant one.
I thought with AST we should be past banning keywords as function etc. names, aren't we?
> 3. If we can fix the switch statement semantics in the future the two > will only differ in that one returns a value and the other one > doesn't. This is a much smaller distinction than functions/closure.
We don't need to "fix" anything in the existing switch, and likely couldn't because of immense BC breakage it would cause. If you want different switch, you better start with different keyword.
> 4. If we'd every want to add pattern matching, we'd still have a > keyword available to us.
What you proposing essentially *is* pattern matching, just very limited one. Extending it would be a natural development.
> type coercion. This would make it a replacement of the switch > statement instead of an addition.
I don't think we need replacement for switch statement. Adding simple matching expression seems to be a good idea, but please don't let it get out of the way and turn into something that is too complex and controversial and be buried under details that nobody actually needs. It's better to have sometime simple.
-- Stas Malyshev smalyshev@gmail.com

Larry Garfield

6 years ago
On Sun, Mar 29, 2020, at 5:07 PM, Ilija Tovilo wrote:
> Hi Larry > > Thanks for your suggestion. > > I chose to use switch instead of match for a couple of reasons: > > 1. It uses the same AST, code generation and opcodes as the switch, I > don't agree that it is significantly different than the switch that we > already have. > 2. Adding the `match` keyword is a breaking change, not an insignificant one. > 3. If we can fix the switch statement semantics in the future the two > will only differ in that one returns a value and the other one > doesn't. This is a much smaller distinction than functions/closure. > 4. If we'd every want to add pattern matching, we'd still have a > keyword available to us. > > > I don't believe that's the case here, however. `switch` is a language construct for a *statement*, which branches the flow of control of the program. > > > > What you're proposing is a language construct for an *expression*, which evaluates depending on internal logic to a different value. > > Well, I encourage you to look at the implementation. You'll see that > the two are in fact very similar. > > I'm not averse to using the match keyword if people are ok with the BC > change. If we do though we definitely should make it work as a > statement, allow blocks in addition to single expression and also fix > type coercion. This would make it a replacement of the switch > statement instead of an addition. > > Ilija
Stas already replied and I agree with his statements, so I won't repeat them. I will, however, add a bit more: As Stas said, that the internal implementation is nearly the same is irrelevant from a language design perspective; the behavior of the syntax for the user is what matters. Essentially, its "user interface". That should be the driving factor. Introducing a new keyword in a major release seems entirely within scope. We've added keywords in minors before, too, so I'm not too worried. As to your other points: * Being strictly typed rather than coercively typed: I'd be fine with this, frankly. There may be an argument to be made that it should respect the declare statement, or something else, but making it a new construct puts using === on the table, and I'd be fine with it. * Work as a statement; allow blocks. I disagree with these strongly, for very closely related reasons. 1) match/select/whatever it's called is not a statement. It is an expression. It evaluates to something. That's its value. if you just want something that's a control statement, we already have switch. That's it's value. Not everything has to be a statement. In fact, I've seen arguments before that statements in general are a bad idea and everything should be an expression. There's a reasonably good language design argument to be made there, but at the very least we shouldn't be pretending that statements are intrinsically superior. They're very much not. The point of statements is to make some change to the state of the system. The point of an expression is to be evaluated into another, simpler value. match() is, specifically, an expression that evaluates to a value. sticking statements inside an expression is just kinda weird, and opens up the door to all kinds of side effect behavior. If you want side effects... you already have switch. 2) The use of a single-expression is, I would argue, a feature, not a defect. As above, expressions evaluate and you usually do not want them to have side effects. Allowing a block on the right side means: * It encourages side effect code. * It makes the construct visually larger, not smaller. That makes it harder to read and understand. * It discourages refactoring of multiple operations into a single operation, viz, a function call. * You have to have either have a return statement or a magic Rust/Ruby-like "last evaluation gets returned" behavior, which is unprecedented in PHP. I would argue that in the match() use cases, any time you may be tempted to use a multi-line expression what you really want is a stand-alone function. It creates a cleaner visual in place, plus encourages breaking up code into smaller, named, self-documenting bits. Those could be named functions, anon functions, method calls, etc. If you have a block of code that ends in a return... it's a function. We have ample ways to define functions already; there's no need to add another one-off way. Viz, if I see this: match($foo) { 5 => { $thing = getThing($foo); $b = $thing->extract($bar); return $b * 4; }; 6 => 'blah'; } Then I would argue this is superior in every situation: match($foo) { 5 => mapFoo($foo); 6 => 'blah'; } function mapFoo($foo, $bar) { $thing = getThing($foo); $b = $thing->extract($bar); return $b * 4; } As for pattern matching, if it supports an arbitrary left-side expression (or list of expressions) then I'm not clear what else is needed. Isn't this "close enough" to pattern matching that it coves all reasonable use cases: match(true) { $foo < 5 => $someVal; $foo < 10 => $otherVal; $foo <= 100=> $somethingElse; default => $whatever. } Switching on a type would be a bit more verbose as you'd need to repeat instanceof in each match expression, but I think I'm willing to accept that. Now, if we had proper enumerations and the ability to enforce that you exhausted the possible values (a la Rust), then I'd be on board with internalizing the type match. Sadly, I don't see that happening in the near future, much as I would support it. --Larry Garfield

Terje Slettebø

6 years ago
Larry Garfield wrote:
> On Sun, Mar 29, 2020, at 4:04 PM, Ilija Tovilo wrote: > > What you're proposing is a language construct for an *expression*, > which evaluates depending on internal logic to a different value. > > Those are sufficiently distinct that I agree they should have distinct > keywords. Plus, the internal syntax is non-trivial to switch back and > forth between (break vs not, etc.), so it is misleading for people to > present them as two slight variants on the same thing; they're really > quite distinct, and that's OK. > > My recommendation would be to just borrow Rust's keyword: > > $result = match ($var) { > $expression => $expression; > $expression => $expression; > $expression => $expression; > default => $expression; > }
I would tend to side with this. The way I'd interpret this is that the provided value is matched against the value of each expression, which also means arbitrary comparisons may be used without additional syntax or special cases, as has been mentioned in this thread: $temp=get_temperature(); $phase = match(true) { $temp<0 => "Ice", $temp>=0 && $temp<=100 => "Water", $temp>100 => "Steam" } I also agree that we should keep this as an expression, not extending it to be a statement as well. Keep it simple. Should we want to use it as a statement at a later point, that would be possible to add. I also like the exhaustive check, so that the following would give an error (and could be found using static analysis) about not handling other values than 1-3: $result = match($x) // int { 1 => ..., 2 => ..., 3 => ... } Having a new keyword like this opens the door to further changes that moves it away from the way switch works, such as pattern matching and destructuring. Regards, Terje