4 ms·
Errors as return values is only acceptable for code that is so performance-sensitive that you aren't allowed to do dynamic memory allocations. For everything el
by throw10920 5y ago
Errors as return values is only acceptable for code that is so performance-sensitive that you aren't allowed to do dynamic memory allocations. For everything else, conditions+restarts are the correct answer, because errors-as-values restricts you to a single error-handling strategy and couples high-level code to low-level code as a result.
- steveklabnik 5y agoFun history fact: Rust had conditions, a very very very long time ago, but folks didn't use them and found them vaguely confusing, so they were removed.
- throw10920 5y agoThis doesn't mean very much. Most people find the Rust borrow checker "vaguely confusing" (if not very confusing), and most people also wouldn't use it were it not strongly suggested by both the compiler and the community ("suggested" as unsafe Rust exists, but you of all people are aware of that). Conversely, I understand condition systems, and I'm not a very good programmer. (I've tried and failed to learn Rust once already) That's a pretty low upper bound on how hard they are, especially relative to advanced features of languages like Haskell. We're very fortunate that programming language design doesn't advance solely by giving people more of what they already use.
- steveklabnik 5y agoI don't mean to say anything about conditions generally, just to mention that they have existed in non-Lisp languages on occasion. > We're very fortunate that programming language design doesn't advance solely by giving people more of what they already use. Agreed!
- zozbot234 5y agoThe problem is that a system of "conditions + restarts" approaches the generality of fully async code. Go can of course do async well enough via its goroutines.
- the_common_man 5y ago> Errors as return values is only acceptable for code that is so performance-sensitive that you aren't allowed to do dynamic memory allocations Not really. It is acceptable for code whose maintainers value readability and simplicity over everything else. I totally agree that readability and simplicity are quite subjective and this is up to the maintainers. I don't really know what "conditions+restarts" is but a few articles landed me into LISP which I find totally unreadable. So, can you point me to some "conditions+restarts" code that I can understand/appreciate easily? Any language is fine, I just want to understand the concept better since I am more a C/C++/JS programmer. (FWIW, never written Go, but it's easy to read and understand).
- b3morales 5y agoIt's probably sufficient for this conversation to just understand it as try-catch. A function is invoked; if it "signals" (throws) then control moves to a handler that matches the signal (exception); the handler runs and resolves the situation. Of course, Lisp being Lisp, the system is extended to announcer voice FULL. GENERALITY. but in its simplest form it's basically equivalent to exception throwing.
- ThatGeoGuy 5y agoThis is correct (in that the most simplistic case is try-catch). However, the difference between a try-catch and conditions / restarts is that when one signals a condition (exception), the restart (catch) has a continuation from the condition. This allows you to inject an expression into the location where an exception occurred and "restart" your code from that point. Whether you do such a thing or not depends on the code, on the type of condition raised, and on what expressions are valid. So you get a lot more flexibility in how errors are handled across the system. But likewise: more complexity in having to make that choice in the first place. Going farther than this, conditions and restarts are really just a fancy way of packaging delimited continuations. I don't personally know any non-Lisp language that has attempted to package these concepts (maybe Dylan, which is a Lisp-like in its own way but without the syntax?). Going back to the original thought regarding error handling - I think Result<T, Err> type handling is fine and that most languages would be better served by that than having different types of exceptions. Conditions and restarts are powerful but your language has to be very expression focused (i.e. does not use a lot of statements) and it's not really clear that there's been a lot of work on making restarts nice to use. Exceptions in all languages that have them have their own set of associated problems, for what its worth, and it's not as easy to move Lisp features into a non-Lisp as one might believe...
- masklinn 5y ago> Errors as return values is only acceptable for code that is so performance-sensitive that you aren't allowed to do dynamic memory allocations. Exceptions-based code can be zero-cost, if no errors occur, at an increased error-case cost. Using error values pessimises this, and increase branch prediction load (as every callsite is now a branch). So in the common case where errors are extremely rare, exceptions-based error handling can be quite a bit faster than return-value error handling. Which doesn't mean it's preferable, but beware thinking that return values are faster.
- throw10920 5y agoThis is an extremely good point - thank you for your correction!
- preseinger 5y agoError handling is basically orthogonal to performance. If a function call can fail, it should return an error, and that error should be managed by its caller. Any other approach means callstacks are unpredictable, which makes a program way way harder to model.
- throw10920 5y ago> that error should be managed by its caller Objectively false. Correct answer: the error should be managed by the code that makes sense to handle the error. > Any other approach means callstacks are unpredictable Also false. I use conditions regularly, and my callstacks are very predictable - errors bubble upward through the call tree until they're handled. There's nothing simpler. > which makes a program way way harder to model Also false. I have no problem at all modeling and understanding my code rife with conditions.
- preseinger 5y agoYou're wrong on every point. I don't know how to speak to you given the strength of your conviction, so shrug
- throw10920 5y agoI think the fact that you didn't actually refute any of my points speaks volumes about which of us is correct.
- preseinger 5y agoWhat do you mean by "conditions"? > errors bubble upward through the call tree until they're handled. There's nothing simpler Between output_a := f1(input_a) // f1 throws exceptions on error output_b := f2(output_a) // f2 throws exceptions on error and output_a, err := f1(input_a) // inspect and deal with err output_b, err := f2(output_a) // inspect and deal with err it seems uncontroversial to me that the latter is less complicated than the former, and easier to model control flow paths. Exceptions make sense in a lot of contexts! Just not services, servers, or long-lived processes.
- astrange 5y agoDoesn't adding conditions/restarts require your language to have stackful continuations? Those are quite complicated unless you don't bother to make them safe (like C doesn't bother to with setjmp/longjmp). I would be nervous about them for the same reason I am about exceptions. Also, the restart seems to imply that you should "handle" the error, but I think this is really overemphasized cause what are you actually going to do about it? There's nothing to do about a lot of errors except die, but this encourages programmers to just make something up they think might help.