3 ms·
Which language(s) did exceptions the best, in your opinion?
by shepmaster 5y ago
Which language(s) did exceptions the best, in your opinion?
- bartwe 5y agoI'd say c#, though the lack of a hard 'nothrow' constraint is annoying.
- fouric 5y agoNot OP, but: I think that there's a pretty strong case to be made that conditions, implemented in Common Lisp, are the best implementation of "exceptions"...by not actually being exceptions. Conditions (plus restarts) have two advantages over exceptions (as implemented in almost every other language, including Go and C#) and Rust's error system that neither can replicate: they allow you to selectively keep the stack wound (both avoiding expensive recomputation and keeping error context), and they separate out multiple error recovery strategies from each other, and allow you to pick which one you want at runtime. The latter fixes a core problem of all other error-handling systems: that the high-level code usually has the context to determine why a low-level operation was happening, and therefore how the error should be recovered from - but it doesn't (or shouldn't) have knowledge of what low-level operations are happening, because that breaks encapsulation. Meanwhile, the low-level code obviously knows about itself - but it doesn't have the high-level context necessary to determine which of several error-recovery strategies to take. A condition system exposes multiple named restarts, which are error-recovery strategies, at the low-level code (technically, you can place them everywhere), and then the higher-level code can choose among those restarts (with names like "abort", "retry", "continue", "ignore") when an error occurs, based on the high-level context and the condition type (because conditions, like exceptions, also have types). Note that these restarts do not necessarily unwind the stack. The stack can remain "wound" to the point of the error, or it can be partially unwound if a restart a few frames up is selected. Exceptions allow none of this - the moment an exception (or a Rust error type) is thrown/returned, the stack is unwound up to the error-handling point, and all context not explicitly encoded is lost (as well as the possibility of retrying an operation (perhaps with different parameters)). Short example: let's say you're writing parser for a log format that goes line-by-line. There's the high-level "read-log-file" function, that calls several other functions that eventually calls a "parse-log-file-line" function. From the local perspective of just that low-level function, if it's fed a line it can't parse, there are multiple valid error-recovery strategies: it could try to repair the line as best as it could, it could add the corrupted line to a list, it could skip the line, or it could explode and die. With a normal exception system, you have to either thread a dedicated argument specifying the strategy down to that function, hard-code it to pick a particular strategy, or re-write that function for your particular application. With conditions, the parse-log-file-line function could throw a condition INVALID-LINE and expose each of the previous options as restarts, and the high-level code could pick one of them, or even handle the condition itself! Longer examples: https://lisper.in/restarts https://lisper.in/restarts https://gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html https://gigamonkeys.com/book/beyond-exception-handling-condi...
- zozbot234 5y agoThe Common Lisp condition system is just an implementation of composable effects, which are useful for plenty of other stuff beyond error handling.
- fouric 5y agoIt sure would be nice if we could even "just" get them for error-handling... I didn't know that that's what their real name was. Is there a place that I can read an easy-to-understand explanation of them without a lot of math?
- FridgeSeal 5y agoNot identical, but very related, this is an article on algebraic effects that’s really straightforward and well-written: https://overreacted.io/algebraic-effects-for-the-rest-of-us/ https://overreacted.io/algebraic-effects-for-the-rest-of-us/
- zvrba 5y agoI like the article, though... the problems it illustrates with effects are usually solved by DI/IoC containers. It even says so: > Effect handlers let us decouple the program logic from its concrete effect implementations without too much ceremony or boilerplate code. For example, we could completely override the behavior in tests to use a fake filesystem [..] Yes, I understand the fundamental difference between effects and DI (effects suspend the execution and search the call stack for the handler - like exceptions, but without immediate unwinding). Maybe we could solve the problem with code being able to throw arbitrary exceptions by injecting an "Error" interface as well and then using some discipline? (I.e., instead of throwing stuff directly, we invoke methods on the Error interface.) Actually, I have to think more about this. Thanks for the link, it gave me some food for thought.
- quotemstr 5y agoCommon Lisp, as another commentator described in detail below. Common Lisp is a state of grace from which we have shamefully fallen. It's old out here, east of Eden.