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