On Tue, 6 Feb 2024 at 19:14, Larry Garfield <la...@garfieldtech.com> wrote:

> On Tue, Feb 6, 2024, at 4:13 PM, Григорий Senior PHP / Разработчик Web
> wrote:
> > Btw, i agree about Javascript, but on a low level it produces the most
> > clean code, because there's no types and rules. All types moved to
> > TypeScript's client side compiler.
> >
> > JS 15 years ago ACCIDENTALLY created a pipeline. Named it "Promise". We
> > spent years after to understand that while (true) and then/catch should
> be
> > different patterns.
>
> I assume much of this thread is a language-barrier issue, which is making
> it more hostile than it needs to be.  So let me try and expand a bit,
> because I am actually quite sympathetic to the OP's request, though not the
> way it's being made.
>
> First of all, please don't top post.  It is considered rude on this list.
> GMail makes it a bit annoying to bottom post but it can be done.  Please do
> so.
>
> Second, there's considerable prior art and discussion on the topic of
> error handling and exceptions.  In particular, I recommend this excellent
> article by Joe Duffy:
>
> https://joeduffyblog.com/2016/02/07/the-error-model/
>
> And this one from me:
>
> https://peakd.com/hive-168588/@crell/much-ado-about-null
>
> Yes, they're long, but error handling is a topic that requires more than
> casual thought.
>
> To summarize the articles for the short of time:
>
> * Exceptions in many languages (including PHP) are very expensive. The
> stack trace is one of the most expensive things PHP does.  This is one of
> the reasons why exceptions are terrible for flow control.
> * Unchecked exceptions (where a function doesn't define what it can throw)
> are a great way to break your application in exciting and unpredictable
> ways.  (This is the other reason exceptions are terrible for flow control.)
> * Many people find checked exceptions cumbersome, even though they are
> better (for reasons Duffy goes into).  This is due mostly to bad design of
> checked exceptions in JVM languages.
> * The real problem is that we have a channel for a success case (return
> value), a channel for catastrophic failure (exceptions), but no channel for
> mundane errors (things a responsible developer should expect and know how
> to handle gracefully).  So those get shoved into one or the other, usually
> an exception.
>
> The need is for a "mundane error" channel.  I agree with this.  Different
> languages handle it in different ways.
>
> * Go has multi-returns.
> * Rust has the Result type (which is an Either monad), and an
> auto-propagation operator (?).
> * PHP has union types (though that's not a deliberate design, just an
> emergent one).
>
> One of the key differences between different approaches is whether they
> force you to handle error cases (Result type) or let you ignore errors and
> assume a happy path (Go, PHP), letting an unhappy path just explode later
> on.  Whether the language should force you to think about unhappy paths is
> a complex and subjective question that I won't delve into now beyond saying
> that there's valid arguments and use cases for both designs.
>
> As noted in the article above, I've started using enums and union type
> returns a lot for error handling and it's pretty nice, all things
> considered.  That works today in all supported PHP verisons (8.1+).  That
> said, it's not perfect, in part because it's not standardized and there's
> no really good language-level automation around it.
>
> If we had generics and ADTs, building a Rust-like Result type would be
> super easy, and I'd suggest we include one in the stdlib for consistency.
> We'll probably get ADTs eventually, but generics are not something I'd bank
> on, so that is out.
>
> HOWEVER, and this is where the important part lies, an open, lightweight,
> checked exception system is isomorphic to a Result object.  It's just
> unwrapped.  Compare these hypotheticals:
>
> class DivByZero {}
> (this could also be an enum case, but being generic for now.)
>
> function divide(float $a, float $b): Result<int, DivByZero>
> {
>     if ($b === 0) return new Result::Err(new DivByZero());
>    return new Result::OK($a/$b);
> }
>
> $result = divide(5, 0);
> $x = match ($result) {
>   is Result::OK($x) => $x,
>   is Result::Err => // Do some kind of error handling.
> }
>
> vs.
>
> function divide(float $a, float $b): int raises DivByZero
> {
>     if ($b === 0) raise new DivByZero();
>    return new $a/$b;
> }
>
> try {
>   $result = divide(5, 0);
>   // Do stuff with $result, knowing it is valid.
> } catch (DivByZero) {
>   // Do some kind of error handling.
> }
>
> These two samples *are logically identical*, and even have mostly the same
> performance characteristics, and both expose useful data to static
> analyzers.  They're just spelled differently.  The advantage of the second
> is that it could be implemented without generics.  (ADTs would be an
> optional nice-to-have.)  And if the caller doesn't handle DivByZero, it
> would try to pass it up to its caller, but being checked it would require
> the caller to also declare that it can raise DivByZero.
>
> The second option could also be improved by other syntactic sugar to make
> it easier to work with, like Rust has.  For example:
>
> try $result = divide(5, 0) catch (DivByZero) { // Error handling that
> evaluates to a value }
>
> Or by making null-aware operators (??, ?->, etc.) treat raised
> light-exceptions as if they were null to make pipelining easier.  Or
> various other ideas I'm just giving examples of for the moment to make the
> point, but let's not get off on that tangent.  (I would also note that I
> agree entirely such a system should only support objects, not primitives.)
>
> This would provide a far better alternative to the "returns value on
> success or false on failure" anti-pattern throughout the stdlib, and to the
> common "returns value on success or null on failure or value not found or
> any other possible issue" pattern common in user-space code.  (No, I don't
> expect us to go back and change all of stdlib; just pointing out that it's
> a known language limitation, and this would be the solution.)
>
> To be clear: I really like this concept and have discussed it with others
> before, using almost exactly this syntax.  I have not proposed it because
> my read of Internals lately is that there's no stomach for more
> type-centric behavior, especially with the obvious "But we already have
> exceptions, what's yer problem?" response (which is valid to a point, but
> also incomplete for reasons explained above).  The responses in this thread
> so far confirm that fear, but as an optimist I'd be very happy to be proven
> wrong if there is an appetite for improving error handling via the type
> system.
>
> Absent that, union types and enums (or really any interfaced object) or a
> purpose-built Either object are the best options today, and while they're
> not ideal, they're not bad options either.
>
> None of that logic or argument requires sh*tting on OOP as a concept or
> abusing others on the list, however.  Doing that only undermines the valid
> point that there is ample headroom to improve PHP's error handling.
>
> --Larry Garfield
>
> --
> PHP Internals - PHP Runtime Development Mailing List
> To unsubscribe, visit: https://www.php.net/unsub.php
>
>
Thank you Larry for this interesting summary - didn't remember there was
quite a bit a discussion around the topic prior.

I lean on the "we have exceptions, just leave it be" side out of practical
reasons - the vast majority of OO code has standardized around the approach
and interoperability is high. It makes using code that's out there super
easy and predictable - almost nobody uses the "return false|0|-1" out there
(at least I haven't used code like that except the PHP's stdlib, and even
that has been changing little by little). It makes error handling
predictable, and considering the type of code we mostly write in PHP - most
of the time we leave the catching to the global top-level handler or
sentry/bugsnag/etc libraries.
Consistency is the word I want to highlight here. For better or for worse -
it's the method the PHP ecosystem arrived at and it's the predominant one.
Introducing a distinctly different method of error handling is going to
bring in wrappers around libraries that convert errors to one style or the
other, the application code can end up using different ways of error
handling, etc, etc. My approach is to grab a different language aka "the
right tool for the job" if I want to build things differently, that's why
we have so many programming languages and not just a few :)

I'd put resources into optimising the VM and php engine to handle the
exceptions better and if there are improvements to be had there - do those
maybe? (I suspect JIT is also going to influence this a lot going forward).

Sincerely,
Arvīds Godjuks

+371 26 851 664
arvids.godj...@gmail.com
Telegram: @psihius https://t.me/psihius
        • Re: ... Alexander Pravdin
          • ... Григорий Senior PHP / Разработчик Web
            • ... Alex Wells
            • ... Robert Landers
  • Re: [PHP-DEV] Fea... Alex Wells
    • Re: [PHP-DEV... Григорий Senior PHP / Разработчик Web
      • Re: [PHP... Arvids Godjuks
        • Re: ... Григорий Senior PHP / Разработчик Web
          • ... Григорий Senior PHP / Разработчик Web
            • ... Larry Garfield
              • ... Arvids Godjuks
              • ... Jordan LeDoux
              • ... Григорий Senior PHP / Разработчик Web
              • ... Larry Garfield
              • ... Robert Landers
              • ... Григорий Senior PHP / Разработчик Web
              • ... Robert Landers
              • ... Larry Garfield
              • ... Григорий Senior PHP / Разработчик Web
              • ... Григорий Senior PHP / Разработчик Web
              • ... Larry Garfield

Reply via email to