Common Lisp the Language, 2nd Edition

next up previous contents index
Next: Restarts Up: Survey of Concepts Previous: Handling Conditions

29.3.4. Object-Oriented Basis of Condition Handling

Of course, the ability of the handler to usefully handle an exceptional situation is related to the quality of the information it is provided. For example, if all errors were signaled by

(error "some format string")

then the only piece of information that would be accessible to the handler would be an object of type simple-error that had a slot containing the format string.

If this were done, string-equal would be the preferred way to tell one error from another, and it would be very hard to allow flexibility in the presentation of error messages because existing handlers would tend to be broken by even tiny variations in the wording of an error message. This phenomenon has been the major failing of most error systems previously available in Lisp. It is fundamentally important to decouple the error message string (the human interface) from the objects that formally represent the error state (the program interface). We therefore have the notion of typed conditions, and of formal operations on those conditions that make them inspectable in a structured way.

This object-oriented approach to condition handling has the following important advantages over a text-based approach:

Some condition types are defined by this document, but the set of condition types is extensible using define-condition. Common Lisp condition types are in fact CLOS classes, and condition objects are ordinary CLOS objects; define-condition merely provides an abstract interface that is a bit more convenient than defclass for defining conditions.

Here, as an example, we define a two-argument function called divide that is patterned after the / function but does some stylized error checking:

(defun divide (numerator denominator) 
  (cond ((or (not (numberp numerator)) 
             (not (numberp denominator))) 
         (error "(DIVIDE '~S '~S) - Bad arguments." 
                numerator denominator)) 
        ((zerop denominator) 
         (error 'division-by-zero 
                :operator 'divide 
                :operands (list numerator denominator))) 
        (t ...)))

Note that in the first clause we have used error with a string argument and in the second clause we have named a particular condition type, division-by-zero. In the case of a string argument, the condition type that will be signaled is simple-error.

The particular kind of error that is signaled may be important in cases where handlers are active. For example, simple-error inherits from type error, which in turn inherits from type condition. On the other hand, division-by-zero inherits from arithmetic-error, which inherits from error, which inherits from condition. So if a handler existed for arithmetic-error while a division-by-zero condition was signaled, that handler would be tried; however, if a simple-error condition were signaled in the same context, the handler for type arithmetic-error would not be tried.

next up previous contents index
Next: Restarts Up: Survey of Concepts Previous: Handling Conditions