INITIALIZE-INSTANCE
.[204]
When using the condition system for error handling, you should define your conditions as subclasses of ERROR
, a subclass of CONDITION
. Thus, you might define malformed-log-entry-error
, with a slot to hold the argument that was passed to parse-log-entry
, like this:
(define-condition malformed-log-entry-error (error)
((text :initarg :text :reader text)))
In parse-log-entry
you'll signal a malformed-log-entry-error
if you can't parse the log entry. You 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. The former is occasionally useful for resignaling an existing condition object, but the latter is more concise. Thus, you could write parse-log- entry
like this, eliding the details of actually parsing a log entry:
(defun parse-log-entry (text)
(if (well-formed-log-entry-p text)
(make-instance 'log-entry ...)
(error 'malformed-log-entry-error :text text)))
What happens when the error is signaled depends on the code above parse-log-entry
on the call stack. To avoid landing in the debugger, you must establish a parse-log-entry
. 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. The function can decline to handle the condition by simply returning normally, in which case control returns to the SIGNAL
function, which will search for the next most recently established handler with a compatible type specifier. To handle the condition, the function must transfer control out of SIGNAL
via a HANDLER-CASE
establishes this kind of condition handler. The basic form of a HANDLER-CASE
is as follows:
(handler-case
where each
(
If the HANDLER-CASE
. The body of a HANDLER- CASE
must be a single expression; you can use PROGN
to combine several expressions into a single form. If, however, the expression signals a condition that's an instance of any of the HANDLER-CASE
. The
For instance, one way to handle the malformed-log-entry-error
signaled by parse- log-entry
in its caller, parse-log-file
, would be to skip the malformed entry. In the following function, the HANDLER-CASE
expression will either return the value returned by parse-log-entry
or return NIL
if a malformed-log-entry-error
is signaled. (The it
in the LOOP
clause collect it
is another LOOP
keyword, which refers to the value of the most recently evaluated conditional test, in this case the value of entry
.)
(defun parse-log-file (file)
(with-open-file (in file :direction :input)
(loop for text = (read-line in nil nil) while text
for entry = (handler-case (parse-log-entry text)
(malformed-log-entry-error () nil))
when entry collect it)))
When parse-log-entry
returns normally, its value will be assigned to entry
and collected by the LOOP
. But if parse-log-entry
signals a malformed-log-entry-error
, then the error clause will return NIL
, which won't be collected.
JAVA-STYLE EXCEPTON HANDLING
HANDLER-CASE
is the nearest analog in Common Lisp to Java- or Python-style exception handling. Where you might write this in Java:
try {
doStuff();
doMoreStuff();
} catch (SomeException se) {
recover(se);
}
or this in Python:
try:
doStuff()
doMoreStuff()
except SomeException, se:
recover(se)
in Common Lisp you'd write this:
(handler-case
(progn
(do-stuff)
(do-more-stuff))
(some-exception (se) (recover se)))
This version of parse-log-file
has one serious deficiency: it's doing too much. As its name suggests, the job of parse-log-file
is to parse the file and produce a list of log- entry
objects; if it can't, it's not its place to decide what to do instead. What if you want to use parse-log-file
in an application that wants to tell the user that the log file is corrupted or one that