5 ms·
My problems with Result/expected/etc 1. They generate syntactic noise at every point they touch the call graph: function signatures, calls, returns. 2. In par
by edflsafoiewq 5y ago
My problems with Result/expected/etc
1. They generate syntactic noise at every point they touch the call graph: function signatures, calls, returns.
2. In particular, if a change causes a deeply nested function that used to always succeed to be able to error, the entire path up the call graph needs to get Resultified.
3. Since the caller must be aware of them, generic code generally has to be Result-aware too.
4. They aren't a total solution, exceptions usually have to exist anyway (eg panic), for things like oom/assert/etc. So you're usually paying the cost of them anyway.
5. You only get what the callee gives you. With exceptions, you can get a backtrace to the cause of the error by default, with no effort needed on the part of the callee.
- zozbot234 5y ago> They generate syntactic noise at every point they touch the call graph: function signatures, calls, returns. This is not "noise" but needed information. A function that can error out should not have the same signature as one that will never return an error. Similarly, call-site special syntax (like '?' in Rust) helps address the concerns raised by hidden control flow. > Since the caller must be aware of them, generic code generally has to be Result-aware too. True, but the Rust standard library includes a zero type that can be used to mark a Result-aware function as infallible, making it easy to wrap it with a non-Result type.
- stickfigure 5y ago> This is not "noise" but needed information. That is incredibly domain-dependent. In most general business processing, that information is just noise. Building a web app? 99.9% of the time, you let exceptions get caught by the http server and return 500 to the client. In a few rare cases where you want to do something else, you catch. If you don't catch, the client still gets 500 - a perfectly acceptable fallback. Building a GUI app? 99.9% of the time, exceptions in the UI loop should just display an error message to the user in a modal dialog and then get ignored. Sure, you can do something else, but the error dialog is a reasonable fallback. There is no good reason to torture the whole call stack to accommodate these problem domains.
- musingsole 5y ago> one that will never return an error Code that is error-safe is so rare. Why adopt a pattern that elevates the normal case ("here be errors") to information you have to disclose at every turn?
- lanstin 5y agoOh my, this sentiment is common. Errors can't happen inside a Turing machine. The errors are just when you step outside the process to interact with externalities. Computations should be thought of as having errors in it them, any more than the integers do. Network or disk calls maybe have all sorts of things happen. You have to think about the failures whenever you have succumbed to reaching outside of your call stack for answers. Because those remote or shared by other processes resources might not reach the same level of certainty, and your perfect code might or might not need the answers or be able to work around their lack.
- musingsole 5y ago> You have to think about the failures whenever you have succumbed to reaching outside of your call stack for answers. For any meaningful application, you'll be reaching out of your call stack every fifth line of code.
- wvenable 5y ago> A function that can error out should not have the same signature as one that will never return an error. Functions that never fail are pretty rare compared to ones that do. It's easier to just assume that all functions can fail. Mentally it's a much simpler model.
- lanstin 5y agoThis post is the saddest thing I have ever read in computing. So much power comes about (on the CPU, in the call stack) by being able to assume that things proceed with mathematical tractable properties, as computable functions, plain old general recursion. Distributed things, sure, you have to have protocols and so one to regain the determinism, and even then perfection is no longer achievable, but within your big FSM of CPU/memory/disk, it's all pre-calculated in some platonic ideal of computable, recursively enumerable Platonic world of simplicity and knowability about which we can reason.
- wvenable 5y agoOne errant cosmic particle and all determinism goes out the door as well. The story of computing is not one of mathematics, it's of people and it's of change. Software is about codifying decisions and without perfect knowledge of the universe those decisions are always going to be wrong in some way. And that's even without introducing the infallibility of programmers. Errors are simply part of the process.
- lanstin 5y agoECC and it should be a few cosmic rays :0) but I wouldn't try to write code to work with random bit changes, but implement distributed protocols that can handle a bit of hostility in the data center. Although when the people calling your libraries are bad enough in your call chain, I have found it worth changing the opaque void * from a pointer to a hash value that you look up in an "allocated" structures hash and can return an error rather than dereferencing when they call stuff 5 minutes after calling "de-alloc." (To stop complaints from people calling de-alloc, then calling "do-something" then crashing then blaming my code because I de-ref the opaque pointer.
- gpderetta 5y agoI agree that functions should specify in their signature wether and how they fail, but checked exceptions can do that. Additional call site syntax is indeed just noise. I strongly agree with David Abrahams[1] on this. A better solution would be noexcept regions were the compiler would statically guarantee that they can't be left via exceptional control flow. [1] https://forums.swift.org/t/on-the-proliferation-of-try-and-soon-await/42621 https://forums.swift.org/t/on-the-proliferation-of-try-and-s...
- zozbot234 5y ago"noexcept" regions would be even noisier than the established pattern of non-fallible calls as the default and some lightweight syntax (such as '?' in Rust) to indicate fallibility. Sure, if literally all calls were fallible the '?' or equivalent would be redundant to call syntax, but everyone knows that this is not the case. And it's important that the failure-prone case be acknowledged as such.
- gpderetta 5y agoYou would use noexcept only around any code modifying an invariant you need to protect.
- kccqzy 5y ago> 2. In particular, if a change causes a deeply nested function that used to always succeed to be able to error, the entire path up the call graph needs to get Resultified. This is a pro not a con. It now shows clearly that all these function calls are now failable. Of course at higher levels you may have additional assumptions that you know won't make the low-level functions fail, and you are welcome not to change everything as well. > 4. They aren't a total solution, exceptions usually have to exist anyway (eg panic), for things like oom/assert/etc. So you're usually paying the cost of them anyway. Panics do not need to be caught and handled. You can (and should) transform panics into abort.
- mwcampbell 5y ago> Panics do not need to be caught and handled. You can (and should) transform panics into abort. I think this strong stance needs some justification, especially since it's not the default in Rust.
- lanstin 5y agoIt is in Go Lang which shares error handling philosophy with GP.
- ziml77 5y agoGo panics do not simply abort. "For a real-world example of panic and recover, see the json package from the Go standard library. It encodes an interface with a set of recursive functions. If an error occurs when traversing the value, panic is called to unwind the stack to the top-level function call, which recovers from the panic and returns an appropriate error value (see the ‘error’ and ‘marshal’ methods of the encodeState type in encode.go)." From https://go.dev/blog/defer-panic-and-recover https://go.dev/blog/defer-panic-and-recover
- lanstin 5y agoBut philosophically, they should just abort.
- exdsq 5y agoAn FYI that Monads are useful for removing that (left, right) boilerplate from composed functions. This was the issue I had where they finally clicked for me.
- saurik 5y agoOnce you are using monads you are now doing the thing that person hates, though: removing the control flow from the code (as it got moved into the monad); really the syntax for exceptions is nothing more than a hardcoded-into-the-language error monad.
- exdsq 5y agoThat's very fair, good point.