Re: 'switch-expression' and the 'type guard' unary operator demo
| From: | Kosit Supanyo | Date: | Sun, 20 Oct 2019 15:19:56 +0000 |
| Subject: | Re: 'switch-expression' and the 'type guard' unary operator demo | ||
| References: | 1 2 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-107589@lists.php.net to get a copy of this message | ||
Hi Rowan
Thank you for your reply.
Was this restriction added to make the implementation easier, or because
> you thought it was a useful feature?
>
This is not a restriction but a result of implementation workarounds. Let's
see:
switch ($x) {
case 1:
echo "ONE\n";
break;
case 2:
echo "TWO\n";
break;
}
vs
switch ($x) {
case 1 => print "ONE\n",
case 2 => print "TWO\n",
};
You can recognize the difference of those by looking for
=> right? But
the parser generator (bison) cannot do that in LR mode (it maybe can in GLR
mode but I'm sure that would be unacceptable due to performance losses/more
memory usage). So workarounds have to be used. I did it by simply return
different tokens from the lexer base on previous token before switch.
https://github.com/webdevxp/php-src/blob/switch-expression/Zend/zend_language_scanner.l#L1460
https://github.com/webdevxp/php-src/blob/switch-expression/Zend/zend_language_scanner.l#L1247
And use that different tokens to define rules in the parser definition so
it won't have any shift/reduce conflicts.
https://github.com/webdevxp/php-src/blob/switch-expression/Zend/zend_language_parser.y#L443
https://github.com/webdevxp/php-src/blob/switch-expression/Zend/zend_language_parser.y#L1107
* Different comparisons applied to the same variable/expression, e.g.
> match($user->getScore()) { case <0 => foo(), case >100 => bar(), default
> => baz() }
I'd thought about that feature too. But since I also introduced type guard
operator which uses < token it would have parser conflicts and simple
workarounds cannot apply.
$v = switch ($x) {
case < <int>$y => 1, // shift/reduce conflict here
};
But if type guard was removed or changed to use another token like (:int)
that feature would be easy to implement because currently ZEND_CASE
opcode has extended_value unused so we can use it to store operator data.
Cheers
>
>
On Sun, Oct 20, 2019 at 9:15 PM Rowan Tommins <rowan.collins@gmail.com>
wrote:
> Hi Kosit,
>
> There's some really interesting ideas in here, thanks for sharing them.
>
>
> On 19/10/2019 17:40, Kosit Supanyo wrote:
> > Like function declaration and function expression in JavaScript, if
> > switch appears as first token at statement level it will
> > be recognized
> as
> > statement but if switch is in expression context it will
> > be
> > switch-expression.
> >
> > switch ($expr) {
> > case $cond1 => $result1,
> > case $cond2 => $result2,
> > case $cond3 => $result3,
> > default => $default_result,
> > };
> > // Parse error: syntax error, unexpected '=>' (T_DOUBLE_ARROW)
> >
> > But this is OK.
> >
> > !switch ($expr) {
> > case $cond1 => $result1,
> > case $cond2 => $result2,
> > case $cond3 => $result3,
> > default => $default_result,
> > }; // semicolon is still required because it is an expression
>
>
> This feels like an odd restriction to me, and one that as far as I'm
> aware PHP doesn't have anywhere else. For instance, it might be
> considered bad style, but it's possible to use a ternary operator as an
> abbreviated if statement:
>
> isset($_GET['logout']) ? $session->logout() : $session->extend();
>
> Was this restriction added to make the implementation easier, or because
> you thought it was a useful feature?
>
>
> > You can omit parenthesized expression which is shortcut to `switch
> (true)`.
> > This change applies to switch statement as well.
> >
> > $v = switch {
> > case $x >= 0 && $x <= 100 => 1,
> > case $x >= 100 && $x <= 200 => 2,
> > default => 3,
> > };
> >
> > switch {
> > case $x >= 0 && $x <= 100:
> > doSomething1();
> > break;
> > case $x >= 100 && $x <= 200:
> > doSomething2();
> > break;
> > default:
> > doNothing();
> > break;
> > }
>
>
> This is an interesting idea, given that switch(true) is the documented
> way to do this right now. However, I've always felt switch(true) was
> rather limited in its advantage over if-elseif. I would prefer a syntax
> that reduced the boilerplate for:
>
> * Different comparisons applied to the same variable/expression, e.g.
> match($user->getScore()) { case <0 => foo(), case >100 => bar(), default
> => baz() }
> * Different values compared against the same expression using the same
> operator, e.g. match( $exception instanceOf ) { case FooException =>
> handleFoo(), case BarException => handleBar() }
>
>
>
> > You can also use return and throwXHÖZÛæŠ8
> > NëªN* in result expression. I recalled
> some
> > languages have this feature (but I've forgotten what language). This
> > feature can be very handy and useful in many use cases.
> >
> > $x = 'd';
> > $v = switch ($x) {
> > case 'a' => 1,
> > case 'b' => 2,
> > case 'c' => return true,
> > default => throw new Exception("'$x' is not supported"),
> > };
>
>
> This seems confusing to me, because it mixes statements and expressions
> in a rather unusual way. The "return" case in particular seems ambiguous
> - if $v is visible outside the function, will it have a value assigned
> to it by the "return" branch, and if so what would that value be? I can
> imagine more use cases for the "throw" version, and it's reasonably
> obvious how it would behave, so it might be reasonable to have that one
> special case.
>
>
>
> > Additional feature in the demo patch is the 'type guard' unary operator
> > which is an operator that will perform type check on given value and
> throw
> > TypeError when type mismatch occurred, otherwise return
> > the value as
> is.
> > It has the same precedence as new.
> >
> > $a = 'This is a string';
> > $v = <int>$a; // TypeError: Value is expected to be int, string given
>
>
> This is a very interesting feature, although I think what would be even
> more useful would be syntax to check if something _can_ be cast, i.e.
> the same check you have here, but as an operator evaluating to boolean.
>
> That way, you could write code like this:
>
> if ( <int>$_GET['id'] ) {
> $user = getUser($_GET['id']):
> }
> else {
> echo "Invalid ID provided";
> }
>
> Which would be equivalent (given a type hint on getUser() and no
> strict_types declaration) to this, but without needing to use exceptions
> as flow control:
>
> try {
> getUser($_GET['id']);
> }
> catch ( TypeError $e ) {
> echo "Invalid ID provided";
> }
>
>
> Regards,
>
> --
> Rowan Tommins (né Collins)
> [IMSoP]
>
> --
> PHP Internals - PHP Runtime Development Mailing List
> To unsubscribe, visit: http://www.php.net/unsub.php
>
>