[RFC] switch expression

  109289
March 25, 2020 12:10 ilija.tovilo@me.com (Ilija Tovilo)
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!
  109291
March 25, 2020 14:17 michal.brzuchalski@gmail.com (=?UTF-8?Q?Micha=C5=82_Brzuchalski?=)
Hi Ilija,

śr., 25 mar 2020 o 13:10 Ilija Tovilo 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
  109293
March 25, 2020 14:27 ilija.tovilo@me.com (Ilija Tovilo)
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
  109294
March 25, 2020 14:59 larry@garfieldtech.com ("Larry Garfield")
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
  109295
March 25, 2020 15:29 ilija.tovilo@me.com (Ilija Tovilo)
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
  109296
March 25, 2020 15:46 larry@garfieldtech.com ("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. Endorse. --Larry Garfield
  109297
March 25, 2020 16:06 php@dennis.birkholz.biz (Dennis Birkholz)
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
  109300
March 25, 2020 16:22 ilija.tovilo@me.com (Ilija Tovilo)
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
  109303
March 25, 2020 16:51 cmbecker69@gmx.de ("Christoph M. Becker")
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
  109299
March 25, 2020 16:21 rowan.collins@gmail.com (Rowan Tommins)
On Wed, 25 Mar 2020 at 15:29, Ilija Tovilo 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]
  109301
March 25, 2020 16:28 ilija.tovilo@me.com (Ilija Tovilo)
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
  109306
March 25, 2020 18:24 php@manuelcanga.dev (Manuel Canga)
Hi, internals,


 ---- En mié, 25 mar 2020 17:21:29 +0100 Rowan Tommins collins@gmail.com> escribió ----
 > On Wed, 25 Mar 2020 at 15:29, Ilija Tovilo 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
  109371
March 27, 2020 12:53 gabriel.ostrolucky@gmail.com (Gabriel O)
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".
  109372
March 27, 2020 12:57 ilija.tovilo@me.com (Ilija Tovilo)
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
  109419
March 29, 2020 20:35 smalyshev@gmail.com (Stanislav Malyshev)
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
  109422
March 29, 2020 20:59 tovilo.ilija@gmail.com (Ilija Tovilo)
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
  109423
March 29, 2020 21:00 smalyshev@gmail.com (Stanislav Malyshev)
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
  109426
March 29, 2020 21:04 tovilo.ilija@gmail.com (Ilija Tovilo)
> 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
  109431
March 29, 2020 21:47 george.banyard@gmail.com ("G. P. B.")
On Sun, 29 Mar 2020 at 23:04, Ilija 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
  109432
March 29, 2020 21:50 larry@garfieldtech.com ("Larry Garfield")
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
  109433
March 29, 2020 21:54 smalyshev@gmail.com (Stanislav Malyshev)
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
  109435
March 29, 2020 22:06 george.banyard@gmail.com ("G. P. B.")
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
  109436
March 29, 2020 22:07 tovilo.ilija@gmail.com (Ilija Tovilo)
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
  109438
March 29, 2020 22:22 smalyshev@gmail.com (Stanislav Malyshev)
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
  109470
March 30, 2020 18:10 larry@garfieldtech.com ("Larry Garfield")
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
  109471
March 30, 2020 18:50 tovilo.ilija@gmail.com (Ilija Tovilo)
Hi Larry

> Not everything has to be a statement.
This is not what I meant. What I meant is that it should be usable in a statement context. Since you mentioned Rust, match is always an expression. But you can use it without making use of the return value. This is what I'm suggesting. In PHP this is already for any expression as well. The only difference is that we require a semicolon. https://doc.rust-lang.org/reference/expressions/match-expr.html https://doc.rust-lang.org/reference/statements.html
> sticking statements inside an expression is just kinda weird
This is precisely what Rust does. https://doc.rust-lang.org/reference/expressions/block-expr.html
> If you want side effects... you already have switch.
Yes plus all the negatives mentioned in the RFC. It would be incredibly unfortunate if we created a new construct that fixes all those problems just to be usable only half the time.
> The point of an expression is to be evaluated into another, simpler value.
This is certainly an oversimplification. Function calls are expressions and still have side effects a lot of the time.
> Isn't this "close enough" to pattern matching that it coves all reasonable use cases
I agree. I don't think there's a convincing reason for pattern matching in PHP right now. Nonetheless, this is not pattern matching and we shouldn't call it that. Regards, Ilija