Re: [RFC] switch expression
| From: | Larry Garfield | Date: | Mon, 30 Mar 2020 18:10:36 +0000 |
| Subject: | Re: [RFC] switch expression | ||
| References: | 1 2 3 4 5 6 7 8 9 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-109470@lists.php.net to get a copy of this message | ||
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