Next: Common Lisp Conditions, Up: Common Lisp Condition System [Contents][Index]
not a bug, but rather something occurring that may stop a piece of code from running: a file is missing, a disk is full, a server has crashed, the network is down. However, if the rest of the program is depending on the actions that were going to be taken, then you’d better deal with the error somehow or you will have introduced a bug. So, errors aren’t caused by bugs, but neglecting to handle an error is almost certainly a bug.
In a well-written program, each function is a black box hiding its inner workings. Programs are then built out of layers of functions: high-level functions are built on top of the lower-level functions, and so on. This hierarchy of functionality manifests itself at runtime in the form of the call stack: if high calls medium, which calls low, when the flow of control is in low, it’s also still in medium and high, that is, they’re still on the call stack.
Function boundaries are an excellent place to deal with errors. 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.
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.
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.
The primitive signaling function SIGNAL
implements the mechanism
of searching for an applicable condition handler and invoking its handler
function.
The reason a handler can decline to handle a condition by returning normally
is because the call to the handler function is just a regular function
call–when the handler returns, control passes back to SIGNAL
, which then
looks for another, less recently bound handler that can handle the condition.
If SIGNAL
runs out of condition handlers before the condition is handled,
it also returns normally.
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.
A condition is a lot like an exception in Java or Python except not all conditions represent an error or exceptional situation.
Condition classes are defined with the
DEFINE-CONDITION
macro. The default superclass of classes defined with
DEFINE-CONDITION
is ‘CONDITION’ rather than ‘STANDARD-OBJECT’.
New condition objects are created with MAKE-CONDITION
rather than MAKE-INSTANCE
.
When a condition is signaled, the signaling machinery looks through a list of active condition handlers, looking for a handler that can handle the condition being signaled based on the condition’s class. Each condition handler consists of a type specifier indicating what types of conditions it can handle and a function that takes a single argument, the condition
At any given moment there can be many active condition handlers established at various levels of the call stack. When a condition is signaled, the signaling machinery finds the most recently established handler whose type specifier is compatible with the condition being signaled and calls its function, passing it the condition object.
The handler function can then choose whether to handle the condition.
SIGNAL
function, which
will search for the next most recently established handler with a
compatible type specifier.
SIGNAL
via a nonlocal exit.
Many condition handlers simply want to
unwind the stack to the place where they were established and then run some
code. The macro HANDLER-CASE
establishes this kind of condition handler.
When using the condition system for error handling, you should define your conditions as subclasses of ‘ERROR’, a subclass of ‘CONDITION’.
error
functionYou signal errors with the function ERROR
, which calls
the lower-level function SIGNAL
and drops into the debugger if the
condition isn’t handled.
You can call ERROR
two ways: you can pass it an already instantiated
condition object, or you can pass it the name of the condition class and any
initargs needed to construct a new condition, and it will instantiate the
condition for you.
if SIGNAL
returns, ERROR
invokes the debugger by calling the function
stored in *DEBUGGER-HOOK*
. Thus, a call to ERROR
can never return
normally; the condition must be handled either by a condition handler or in
the debugger.
You place code that actually recovers from errors into restarts, and condition handlers can then handle a condition by invoking an appropriate restart. You can place restart code in mid- or low-level functions, while moving the condition handlers into the upper levels of the application
To change parse-log-file
so it establishes a restart
instead of a condition handler, you can change the HANDLER-CASE
to a
RESTART-CASE
.
The advantage of establishing a restart rather than having parse-log-file
handle the error directly is it makes parse-log-file
usable in more
situations.
Another condition signaling function, an example of a different kind
of protocol built on the condition system. WARN
calls SIGNAL
to signal a
condition. But if SIGNAL
returns, WARN
doesn’t invoke the debugger–it
prints the condition to ‘*ERROR-OUTPUT*’ and returns ‘NIL’, allowing its
caller to proceed.
WARN
also establishes a restart, MUFFLE-WARNING
, around
the call to SIGNAL
that can be used by a condition handler to make WARN
return without printing anything.
A third error-signaling function, CERROR
, provides yet another
protocol. Like ERROR
, CERROR
will drop you into the debugger if the
condition it signals isn’t handled. But like WARN
, it establishes a restart
before it signals the condition. The restart, CONTINUE
, causes CERROR
to
return normally–if the restart is invoked by a condition handler, it will
keep you out of the debugger altogether. Otherwise, you can use the restart
once you’re in the debugger to resume the computation immediately after the
call to CERROR
. The function CONTINUE
finds and invokes the CONTINUE
restart if it’s available and returns ‘NIL’ otherwise.
Next: Common Lisp Conditions, Up: Common Lisp Condition System [Contents][Index]