def parse_log_entry(text, err_fn):
entry = parse(text)
if entry is None:
entry = err_fn(text)
return entry
Instead of passing a function, err_fn could also be some kind of resolver that finds a function for a condition name, or there could be a global recovery function: def parse_log_entry(text):
entry = parse(text)
if entry is None:
entry = recover('invalid_log_entry', text)
return entry<Devil’s advocate>
But if there was some way for medium to recover by itself, it had the option to handle the exception on the way back up the call stack and then to restart the computation that called low, hopefully under improved conditions.
Control will therefore return to high only if medium doesn’t itself know how to handle the error. At that point, the entire computation for which medium was responsible has failed.
Assuming the system as a whole uses reasonable functional decomposition and modular design, since exceptions have limited value under other circumstances anyway, shouldn’t any context that was known only within medium be irrelevant to any recovery action taken at high’s level?
</Devil’s advocate>
This seems like a large step in the right direction for exception handling, but I think it still has the problems that the programmer writing the function that can throw needs to enumerate a number of cases to make it effective, and the programmer calling that function needs to have documentation ready for a descriptions of all the possible restarts.
NB: Google will then have all your base, and people on HN have recommended turning off google web history altogether (let alone the toolbar!). I mention it, because it is also a killer-solution to the common problem you mention.
This is the problem that comes up in many languages and which needs to be dealt with:
"Because each function is a black box, function boundaries are an excellent place to deal with errors. Each function--low, for example--has a job to do. Its direct caller--medium in this case--is counting on it to do its job. However, an error that prevents it from doing its job puts all its callers at risk: medium called low because it needs the work done that low does; if that work doesn't get done, medium is in trouble. But this means that medium's caller, high, is also in trouble--and so on up the call stack to the very top of the program. On the other hand, because each function is a black box, if any of the functions in the call stack can somehow do their job despite underlying errors, then none of the functions above it needs to know there was a problem--all those functions care about is that the function they called somehow did the work expected of it.
In most languages, errors are handled by returning from a failing function and giving the caller the choice of either recovering or failing itself. Some languages use the normal function return mechanism, while languages with exceptions return control by throwing or raising an exception. Exceptions are a vast improvement over using normal function returns, but both schemes suffer from a common flaw: while searching for a function that can recover, the stack unwinds, which means code that might recover has to do so without the context of what the lower-level code was trying to do when the error actually occurred.
Consider the hypothetical call chain of high, medium, low. If low fails and medium can't recover, the ball is in high's court. For high to handle the error, it must either do its job without any help from medium or somehow change things so calling medium will work and call it again. The first option is theoretically clean but implies a lot of extra code--a whole extra implementation of whatever it was medium was supposed to do. And the further the stack unwinds, the more work that needs to be redone. The second option--patching things up and retrying--is tricky; for high to be able to change the state of the world so a second call into medium won't end up causing an error in low, it'd need an unseemly knowledge of the inner workings of both medium and low, contrary to the notion that each function is a black box.
Common Lisp's error handling system gives you a way out of this conundrum by letting you separate the code that actually recovers from an error from the code that decides how to recover. Thus, you can put recovery code in low-level functions without committing to actually using any particular recovery strategy, leaving that decision to code in high-level functions.
To get a sense of how this works, let's suppose you're writing an application that reads some sort of textual log file, such as a Web server's log. Somewhere in your application you'll have a function to parse the individual log entries. Let's assume you'll write a function, parse-log-entry, that will be passed a string containing the text of a single log entry and that is supposed to return a log-entry object representing the entry. This function will be called from a function, parse-log-file, that reads a complete log file and returns a list of objects representing all the entries in the file.
To keep things simple, the parse-log-entry function will not be required to parse incorrectly formatted entries. It will, however, be able to detect when its input is malformed. But what should it do when it detects bad input? In C you'd return a special value to indicate there was a problem. In Java or Python you'd throw or raise an exception. In Common Lisp, you signal a condition.
A condition is an object whose class indicates the general nature of the condition and whose instance data carries information about the details of the particular circumstances that lead to the condition being signaled.3 In this hypothetical log analysis program, you might define a condition class, malformed-log-entry-error, that parse-log-entry will signal if it's given data it can't parse."
http://www.gigamonkeys.com/book/beyond-exception-handling-co...
Read the whole thing. It was a very clever system and wish something like it was available in other languages. (The lack of it in Clojure was one of the few things I agreed with Loper about in his anti-Clojure rant: http://www.loper-os.org/?p=42)