[RFC][DISCUSSION] throw expression

  109204
March 22, 2020 16:16 ilija.tovilo@me.com (Ilija Tovilo)
Hi everybody! I hope you’re doing well.

 

Due to the modest feedback I’d like to move the throw expression RFC to “under discussion”.

https://wiki.php.net/rfc/throw_expression

 

In short, the RFC proposes to convert the throw statement into an expression to make it usable in arrow functions, the coalesce operator, as well as the ternary/elvis operator.

If you have any feedback, concerns, or if you want to express your interest or lack thereof, let me know!

 

Regards
  109205
March 22, 2020 17:16 Danack@basereality.com (Dan Ackroyd)
On Sun, 22 Mar 2020 at 16:17, Ilija Tovilo tovilo@me.com> wrote:
> > Due to the modest feedback I’d like to move the throw expression RFC to “under discussion”. > > https://wiki.php.net/rfc/throw_expression >
Regarding the example: $condition || throw new Exception('$condition must be truthy') && $condition2 || throw new Exception('$condition2 must be truthy'); The "Deprecate left-associative ternary operator"* RFC made it so that parentheses are required when ternary operators are nested in complicated statements. Would a similar requirement for parentheses around complicated throw expressions be a suitable solution to avoid people being surprised by the behaviour? cheers Dan Ack * https://wiki.php.net/rfc/ternary_associativity
  109206
March 22, 2020 18:14 mike@newclarity.net (Mike Schinkel)
> On Mar 22, 2020, at 1:16 PM, Dan Ackroyd <Danack@basereality.com> wrote: > > On Sun, 22 Mar 2020 at 16:17, Ilija Tovilo tovilo@me.com> wrote: >> >> Due to the modest feedback I’d like to move the throw expression RFC to “under discussion”. >> >> https://wiki.php.net/rfc/throw_expression >> > > Regarding the example: > > $condition || throw new Exception('$condition must be truthy') > && $condition2 || throw new Exception('$condition2 must be truthy'); > > The "Deprecate left-associative ternary operator"* RFC made it so that > parentheses are required when ternary operators are nested in > complicated statements. > > Would a similar requirement for parentheses around complicated throw > expressions be a suitable solution to avoid people being surprised by > the behaviour? >
Why can't you just do this in userland code? function throwException(Exception $exception) { throw $exception; } $callable = fn() => throwException( new Exception() ); // $value is non-nullable. $value = $nullableValue ?? throwException( new InvalidArgumentException() ); // $value is truthy. $value = $falsableValue ?: throwException( new InvalidArgumentException() ); // $value is only set if the array is not empty. $value = !empty($array) ? reset($array) : throwException( new InvalidArgumentException() ); -Mike P.S. I am probably in the vast minority on this list but I would like to see fewer places that throw exceptions, not more. I want to deal with errors where they happen, not throw exceptions or have to catch them as I have found that I can write much more robust and easier to read code when I write without using exceptions. I came to this realization because of learning that Go does not endorse exceptions and then I learned why they do not which strongly resonated with me. After that, I finally felt comfortable saying that "Exceptions seemed like a good idea at the time." I now have a whole slew of classes who only purpose is to wrap PHP functions and classes that throw exceptions so I can call them w/o having to use try{}catch{}. Instead I use an if() afterwards to check and then handle it if there was an error. One particularizing problematic exception in PHP is with the DataTime class. I have found it effectively impossible to create a class that extends DateTime without having the potential for an exception to be thrown (unless someone knows a way that I do not?) The potential is actually hypothetical, but PhpStorm nonetheless still complains that I have not handled exceptions when using that child class.
  109208
March 22, 2020 18:39 ilija.tovilo@me.com (Ilija Tovilo)
Hi Mike

Thanks for your feedback!

Your solution works well and it's true that PHP would do just fine without accepting this RFC.
However I do think this RFC makes sense for a few reasons:

* I think that most people would expect some of the examples (especially the arrow function) to be valid PHP code
* Adding a throwException function will unnecessarily fragment your codebase into throw statements and calls to that function
* It's not as static analysis friendly
* The patch has very low complexity

I agree that some languages do well with returning errors instead of throwing them. I personally don't think it's a good fit for PHP because PHP can't enforce you to handle these cases.

Regards

On 22.03.20, 19:14, "Mike Schinkel" <mike@newclarity.net> wrote:

    > On Mar 22, 2020, at 1:16 PM, Dan Ackroyd <Danack@basereality.com> wrote:
    > 
    > On Sun, 22 Mar 2020 at 16:17, Ilija Tovilo tovilo@me.com> wrote:
    >> 
    >> Due to the modest feedback I’d like to move the throw expression RFC to “under discussion”.
    >> 
    >> https://wiki.php.net/rfc/throw_expression
    >> 
    > 
    > Regarding the example:
    > 
    > $condition || throw new Exception('$condition must be truthy')
    >  && $condition2 || throw new Exception('$condition2 must be truthy');
    > 
    > The "Deprecate left-associative ternary operator"* RFC made it so that
    > parentheses are required when ternary operators are nested in
    > complicated statements.
    > 
    > Would a similar requirement for parentheses around complicated throw
    > expressions be a suitable solution to avoid people being surprised by
    > the behaviour?
    > 
    
    Why can't you just do this in userland code?
    
    function throwException(Exception $exception) {
    	throw $exception;
    }
    
    $callable = fn() => throwException( new Exception() );
    
    // $value is non-nullable.
    $value = $nullableValue ?? throwException( new InvalidArgumentException() ); 
    
    // $value is truthy.
    $value = $falsableValue ?: throwException( new InvalidArgumentException() );
    
    // $value is only set if the array is not empty.
    $value = !empty($array)
        ? reset($array)
        : throwException( new InvalidArgumentException() );
    
    
    -Mike
    P.S. I am probably in the vast minority on this list but I would like to see fewer places that throw exceptions, not more. 
    
    I want to deal with errors where they happen, not throw exceptions or have to catch them as I have found that I can write much more robust and easier to read code when I write without using exceptions. I came to this realization because of learning that Go does not endorse exceptions and then I learned why they do not which strongly resonated with me. After that, I finally felt comfortable saying that "Exceptions seemed like a good idea at the time."
    
    I now have a whole slew of classes who only purpose is to wrap PHP functions and classes that throw exceptions so I can call them w/o having to use try{}catch{}. Instead I use an if() afterwards to check and then handle it if there was an error.
    
    One particularizing problematic exception in PHP is with the DataTime class. I have found it effectively impossible to create a class that extends DateTime without having the potential for an exception to be thrown (unless someone knows a way that I do not?)  The potential is actually hypothetical, but PhpStorm nonetheless still complains that I have not handled exceptions when using that child class.
  109210
March 22, 2020 19:25 mike@newclarity.net (Mike Schinkel)
> On Mar 22, 2020, at 2:39 PM, Ilija Tovilo tovilo@me.com> wrote: > > Hi Mike > > Thanks for your feedback! > > Your solution works well and it's true that PHP would do just fine without accepting this RFC. > However I do think this RFC makes sense for a few reasons: > > * I think that most people would expect some of the examples (especially the arrow function) to be valid PHP code > * Adding a throwException function will unnecessarily fragment your codebase into throw statements and calls to that function > * It's not as static analysis friendly > * The patch has very low complexity > > I agree that some languages do well with returning errors instead of throwing them. I personally don't think it's a good fit for PHP because PHP can't enforce you to handle these cases. > > Regards
Hi Ilija, I am fine if other people use exceptions as long as I have ways to not have to use them. The lack of enforcement is not a concern for me, that is what code review is for. However if error handling vs exception handling were elevated to a standardized alternate strategy by PHP then static analysis could catch lack of handling said errors. In the case of allowing people to throw exceptions in expressions that just means for me that I have more code I have to wrap to avoid having to deal with those exceptions throughout my code if and when I use other people's code from Packagist or GitHub. -Mike P.S. However, I think my opinion on exceptions in PHP is in the minority, mine is only one opinion, and I don't even have a vote so I am not sure it matters what my preference is on this topic.
> > On 22.03.20, 19:14, "Mike Schinkel" <mike@newclarity.net> wrote: > >> On Mar 22, 2020, at 1:16 PM, Dan Ackroyd <Danack@basereality.com> wrote: >> >> On Sun, 22 Mar 2020 at 16:17, Ilija Tovilo tovilo@me.com> wrote: >>> >>> Due to the modest feedback I’d like to move the throw expression RFC to “under discussion”. >>> >>> https://wiki.php.net/rfc/throw_expression >>> >> >> Regarding the example: >> >> $condition || throw new Exception('$condition must be truthy') >> && $condition2 || throw new Exception('$condition2 must be truthy'); >> >> The "Deprecate left-associative ternary operator"* RFC made it so that >> parentheses are required when ternary operators are nested in >> complicated statements. >> >> Would a similar requirement for parentheses around complicated throw >> expressions be a suitable solution to avoid people being surprised by >> the behaviour? >> > > Why can't you just do this in userland code? > > function throwException(Exception $exception) { > throw $exception; > } > > $callable = fn() => throwException( new Exception() ); > > // $value is non-nullable. > $value = $nullableValue ?? throwException( new InvalidArgumentException() ); > > // $value is truthy. > $value = $falsableValue ?: throwException( new InvalidArgumentException() ); > > // $value is only set if the array is not empty. > $value = !empty($array) > ? reset($array) > : throwException( new InvalidArgumentException() ); > > > -Mike > P.S. I am probably in the vast minority on this list but I would like to see fewer places that throw exceptions, not more. > > I want to deal with errors where they happen, not throw exceptions or have to catch them as I have found that I can write much more robust and easier to read code when I write without using exceptions. I came to this realization because of learning that Go does not endorse exceptions and then I learned why they do not which strongly resonated with me. After that, I finally felt comfortable saying that "Exceptions seemed like a good idea at the time." > > I now have a whole slew of classes who only purpose is to wrap PHP functions and classes that throw exceptions so I can call them w/o having to use try{}catch{}. Instead I use an if() afterwards to check and then handle it if there was an error. > > One particularizing problematic exception in PHP is with the DataTime class. I have found it effectively impossible to create a class that extends DateTime without having the potential for an exception to be thrown (unless someone knows a way that I do not?) The potential is actually hypothetical, but PhpStorm nonetheless still complains that I have not handled exceptions when using that child class. > >
  109207
March 22, 2020 18:20 ilija.tovilo@me.com (Ilija Tovilo)
Hi Dan

Interesting suggestion. I'll have to think more about how many of these variations there are and if there's an easy way to recognize them.

Ilija

On 22.03.20, 18:16, "Dan Ackroyd" <Danack@basereality.com> wrote:

    On Sun, 22 Mar 2020 at 16:17, Ilija Tovilo tovilo@me.com> wrote:
    >
    > Due to the modest feedback I’d like to move the throw expression RFC to “under discussion”.
    >
    > https://wiki.php.net/rfc/throw_expression
    >
    
    Regarding the example:
    
    $condition || throw new Exception('$condition must be truthy')
      && $condition2 || throw new Exception('$condition2 must be truthy');
    
    The "Deprecate left-associative ternary operator"* RFC made it so that
    parentheses are required when ternary operators are nested in
    complicated statements.
    
    Would a similar requirement for parentheses around complicated throw
    expressions be a suitable solution to avoid people being surprised by
    the behaviour?
    
    cheers
    Dan
    Ack
    
    * https://wiki.php.net/rfc/ternary_associativity
  109310
March 25, 2020 19:12 ajf@ajf.me (Andrea Faulds)
Hi Dan,

Dan Ackroyd wrote:
> Regarding the example: > > $condition || throw new Exception('$condition must be truthy') > && $condition2 || throw new Exception('$condition2 must be truthy'); > > The "Deprecate left-associative ternary operator"* RFC made it so that > parentheses are required when ternary operators are nested in > complicated statements. > > Would a similar requirement for parentheses around complicated throw > expressions be a suitable solution to avoid people being surprised by > the behaviour? > > cheers > Dan > Ack > > * https://wiki.php.net/rfc/ternary_associativity >
Is there a strong reason to require parentheses? I would think we can just give `throw` an appropriate precedence so that expressions like the above do what is desired — and then if you wanted to do something unusual, you would have to add parentheses anyway. Regards, Andrea
  109331
March 26, 2020 12:04 ilija.tovilo@me.com (Ilija Tovilo)
On 22.03.20, 18:17, "Dan Ackroyd" <Danack@basereality.com> wrote:

> $condition || throw new Exception('$condition must be truthy') > && $condition2 || throw new Exception('$condition2 must be truthy'); > > The "Deprecate left-associative ternary operator"* RFC made it so that > parentheses are required when ternary operators are nested in > complicated statements. > > Would a similar requirement for parentheses around complicated throw > expressions be a suitable solution to avoid people being surprised by > the behaviour?
I've thought about this some more. There is a reasonable way to recognize this, namely to check if the expression after the throw keyword is of type ZEND_AST_OR or ZEND_AST_AND. The expression above will fail with this message (given that $condition is false): ``` Fatal error: Uncaught Error: Can only throw objects in ... ``` IMO this is clear enough that a message for this edge case is not necessary.
  109212
March 22, 2020 21:32 matthewmatthew@gmail.com (Matthew Brown)
I think this is great.

It reminds me of a construct that Hack introduced called "as expressions":

With throw expressions the Hack assignment,

`$a = $b as string;`

would be equivalent to the PHP assignment

`$a = is_string($b) ? $b : throw new TypeError('$b must be a string');`

If this passes, it would be great to see sugary equivalents get added to
the language too.

On Sun, 22 Mar 2020 at 12:17, Ilija Tovilo tovilo@me.com> wrote:

> Hi everybody! I hope you’re doing well. > > > > Due to the modest feedback I’d like to move the throw expression RFC to > “under discussion”. > > https://wiki.php.net/rfc/throw_expression > > > > In short, the RFC proposes to convert the throw statement into an > expression to make it usable in arrow functions, the coalesce operator, as > well as the ternary/elvis operator. > > If you have any feedback, concerns, or if you want to express your > interest or lack thereof, let me know! > > > > Regards > > > >
  109308
March 25, 2020 19:04 ajf@ajf.me (Andrea Faulds)
Hey Ilija,

Ilija Tovilo wrote:
> Hi everybody! I hope you’re doing well. > > > > Due to the modest feedback I’d like to move the throw expression RFC to “under discussion”. > > https://wiki.php.net/rfc/throw_expression > > > > In short, the RFC proposes to convert the throw statement into an expression to make it usable in arrow functions, the coalesce operator, as well as the ternary/elvis operator.
Thanks for this RFC, I have wanted this sometimes when writing PHP code. The ?? operator is one of those places, but I am surprised you haven't mentioned the `and` and `or` operators. It is classic PHP style (probably borrowed from Perl?) to write e.g.: $mysql = mysql_open(...) or die("Couldn't connect to MySQL"); Obviously not very modern with use of mysql_ ;) But the `or` operator is still quite handy for this kind of trivial error handling, and it would be great to be able to write e.g.: $fp = @fopen($filename, "r") or throw new Exception("Couldn't open file!"); Likewise for `and` (I think it is less common): $somethingWentWrong and throw new Exception("Oh no!"); So I would be happy to see `throw` become an expression for these reasons, even if it is a bit conceptually weird given it never returns anything! Thanks, Andrea
  109327
March 26, 2020 09:34 nikita.ppv@gmail.com (Nikita Popov)
On Sun, Mar 22, 2020 at 5:17 PM Ilija Tovilo tovilo@me.com> wrote:

> Hi everybody! I hope you’re doing well. > > > > Due to the modest feedback I’d like to move the throw expression RFC to > “under discussion”. > > https://wiki.php.net/rfc/throw_expression > > > > In short, the RFC proposes to convert the throw statement into an > expression to make it usable in arrow functions, the coalesce operator, as > well as the ternary/elvis operator. > > If you have any feedback, concerns, or if you want to express your > interest or lack thereof, let me know! >
Looks like a reasonable change to me. Maybe interesting to compare with the ECMAScript and C# proposals you reference: The ECMAScript proposal (https://tc39.es/proposal-throw-expressions/) defines the throw expression as a very high precedence operator, rather than a low precedence one. There's discussion on that in https://github.com/tc39/proposal-throw-expressions/issues/10. Reading that thread, it seems like nobody actually wants the high precedence version, but there are some ECMAScript-specific issues with low precedence, because of conflicts with comma expressions. Thankfully that doesn't apply to us. The C# proposal ( https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-7.0/throw-expression) limits the use of throw expressions to specific context only (in short lambdas and on the right hand side of ?: and ??). I'm personally not a fan of that, unless there's a technical motivation for that. If it's just about enforcing coding style, there's already plenty of weird expressions you can write right now. I don't want to add special cases to the language specification to enforce someone's style preferences.
> The "Deprecate left-associative ternary operator"* RFC made it so that > parentheses are required when ternary operators are nested in > complicated statements.
I should probably explicitly say here that the parentheses are required not because I think it is a good idea to require them, but to avoid changing the interpretation of an expression from one version to the next. Given that we did end up changing the precedence of the "." operator (rather than requiring parentheses), this choice is somewhat dubious now, and it would have been better to simply change the interpretation of the ternary in PHP 8 as well. Regards, Nikita