> By people who do not want to believe that errors are part of a system's API…
The point was that you should be returning errors, not throwing them. Runtime exceptions (null reference, division by zero, out of memory, etc.) ought to indicate a fatal error in the (sub)program or runtime environment. You can trap these, and report them, but it's usually a mistake to try to case-match on them. Unlike errors, which are predictable, enumerable elements of the design, runtime exceptions should be treated as an open set.
I disagree with this. But, I'm also a fan of the condition system in Common Lisp.
That is, if the problem is likely one that needs operator/user intervention, the non local semantics of exceptions makes a ton of sense. Indeed, it is useful to have a central handler of "things went wrong" in ways that is cumbersome if every place is responsible for that.
If you read the article by Anders Hejlsberg, he's not arguing against centralized handling of exceptions—the handling of runtime exceptions is expected to be centralized near the main program loop. That, however, is a general-purpose handler which won't have much logic related to any particular kind of exception; it just reports what happened and moves on. You don't need checked exceptions for that.
The condition system in Common Lisp (which I am also a fan of BTW) is designed around dealing with conditions when they occur, whereas most of the alternatives focus on the aftermath. In particular, conditions don't unwind the stack before running their handlers, which makes it possible to correct the issue and continue, though handlers can naturally choose to perform non-local returns instead. More to the point, there is no requirement to annotate Common Lisp functions with the conditions they may raise, which makes them more akin to unchecked exceptions.
Fair. Sounds like you are more claiming that most functions would be better returning a result type, but some will be better with more?
I view this as I want my engine to mostly just work. It may need to indicate "check engine" sometimes, though. And that, by necessity, has to be a side channel?
I think that is my ultimate dream. I want functions to have a side channel to the user/operator that is not necessarily in the main flow path. At large, I lean on metrics for this. But sometimes there are options. How do you put those options in, without being a burden for the main case where they are not relevant?
> I want functions to have a side channel to the user/operator that is not necessarily in the main flow path.
That is the essence of the Common Lisp condition system, and you can get there in most languages with closures, or at least function pointers, and exceptions or some other non-local return mechanism using a combination of callback functions for the conditions and unchecked exceptions for the default, unhandled case. The key is that you don't try to catch the exceptions, except at the top level where they are simply reported to the user. Instead you register your condition handler as a callback function so that it will be invoked to resolve the issue without unwinding the stack. It helps to have variables with dynamically-scoped values for this, though you can work around the absence of first-class support as long as you have thread-local storage.
C++ actually uses this model for its out-of-memory handling. You can register a callback with std::set_new_handler() to be invoked if memory allocation with `operator new` fails; if it returns then allocation is retried, and only if there is no handler is an exception thrown. Unfortunately this approach didn't really catch on in other areas.
I'm not sure callbacks alone can be equivalent to a resumable conditions system. You really need full coroutines in the general case. Anyway, what you are proposing is more of a partial alternative to exceptions, since the caller has to be aware of what's 'handled' in advance, whereas conditions may additionally unwind up to a predefined restart point or fail up to the caller similar to a non-handled exception.
> I'm not sure callbacks alone can be equivalent to a resumable conditions system.
I agree, but I was not relying solely on callbacks. You do need some form of non-local return (such as exceptions or continuations) to implement the "resumable" aspect with a choice of restart points, in addition to the callbacks.
> You really need full coroutines in the general case.
I'm having a hard time imagining an error-handling scenario that would require the full power of coroutines—in particular the ability to jump back into the condition handler after a restart. In any case, most languages (even C, if you work at it) can express coroutines in some form or another.
> Anyway, what you are proposing is more of a partial alternative to exceptions, since the caller has to be aware of what's 'handled' in advance, whereas conditions may additionally unwind up to a predefined restart point or fail up to the caller similar to a non-handled exception.
Clearly there has been a breakdown in communication, as I though this was exactly what I described. The handler callbacks are "ambient environment" (i.e. per-thread variables with dynamically-scoped values) so there is no particular need for the caller to be aware of them unless it wishes to alter the handling of a particular condition. Restart points can be implemented by (ab)using unchecked exceptions for control flow to unwind the stack, or more cleanly via continuations if the language supports them.
The point was that you should be returning errors, not throwing them. Runtime exceptions (null reference, division by zero, out of memory, etc.) ought to indicate a fatal error in the (sub)program or runtime environment. You can trap these, and report them, but it's usually a mistake to try to case-match on them. Unlike errors, which are predictable, enumerable elements of the design, runtime exceptions should be treated as an open set.