Common Lisp the Language, 2nd Edition

Next: Creating Conditions Up: Program Interface to Previous: Handling Conditions

## 29.4.5. Defining Conditions

[The contents of this section are still a subject of some debate within X3J13. The reader may wish to take this section with a grain of salt, two aspirin tablets, and call a hacker in the morning.-GLS]

[Macro]

    define-condition name ({parent-type}*)
                     [({slot-specifier}*) {option}*]

Defines a new condition type called name, which is a subtype of each given parent-type. Except as otherwise noted, the arguments are not evaluated.

Objects of this condition type will have all of the indicated slots, plus any additional slots inherited from the parent types (its superclasses). If the slots list is omitted, the empty list is assumed.

A slot must have the form

    slot-specifier ::= slot-name | (slot-name [[?slot-option]])

For the syntax of a slot-option, see `defclass`. The slots of a condition object are normal CLOS slots. Note that `with-slots` may be used instead of accessor functions to access slots of a condition object.

`make-condition` will accept keywords (in the keyword package) with the print name of any of the designated slots, and will initialize the corresponding slots in conditions it creates.

Accessors are created according to the same rules as used by `defclass`.

The valid options are as follows:

`(:documentation doc-string)`

  The doc-string should be either `nil` or a string that describes the purpose of the condition type. If this option is omitted, `nil` is assumed. Calling `(documentation 'name 'type)` will retrieve this information.

`(:report exp)`

  If exp is not a literal string, it must be a suitable argument to the `function` special form. The expression `(function exp)` will be evaluated in the current lexical environment. It should produce a function of two arguments, a condition and a stream, that prints on the stream a description of the condition. This function is called whenever the condition is printed while `*print-escape*` is `nil`.

  If exp is a literal string, it is shorthand for

      (lambda (c s)
        (declare (ignore c))
        (write-string exp s))

  [That is, a function is provided that will simply write the given string literally to the stream, regardless of the particular condition object supplied.-GLS]

  The `:report` option is processed after the new condition type has been defined, so use of the slot accessors within the report function is permitted. If this option is not specified, information about how to report this type of condition will be inherited from the parent-type.

[X3J13 voted in March 1989 (ZLOS-CONDITIONS) to integrate the Condition System and the Object System. In the original Condition System proposal, `define-condition` allowed only one parent-type (the inheritance structure was a simple hierarchy). Slot descriptions were much simpler, even simpler than those for `defstruct`:

    slot ::= slot-name | (slot-name) | (slot-name default-value)

Similarly, `define-condition` allowed a `:conc-name` option similar to that of `defstruct`:

`(:conc-name symbol-or-string)`

  Not now part of Common Lisp. As with `defstruct`, this sets up automatic prefixing of the names of slot accessors. Also as in `defstruct`, the default behavior is to use the name of the new type, name, followed by a hyphen. (Generated names are interned in the package that is current at the time that the `define-condition` is processed).

One consequence of the vote was to make `define-condition` slot descriptions like those of `defclass`.-GLS]

Here are some examples of the use of `define-condition`.

The following form defines a condition of type `peg/hole-mismatch` that inherits from a condition type called `blocks-world-error`:

    (define-condition peg/hole-mismatch (blocks-world-error)
                      (peg-shape hole-shape)
      (:report
        (lambda (condition stream)
          (with-slots (peg-shape hole-shape) condition
            (format stream "A ~A peg cannot go in a ~A hole."
                    peg-shape hole-shape))))

The new type has slots `peg-shape` and `hole-shape`, so `make-condition` will accept `:peg-shape` and `:hole-shape` keywords. The `with-slots` macro may be used to access the `peg-shape` and `hole-shape` slots, as illustrated in the `:report` information.

Here is another example. This defines a condition called `machine-error` that inherits from `error`:

    (define-condition machine-error (error)
                      ((machine-name
                        :reader machine-error-machine-name))
      (:report (lambda (condition stream)
                 (format stream "There is a problem with ~A."
                         (machine-error-machine-name condition)))))

Building on this definition, we can define a new error condition that is a subtype of `machine-error` for use when machines are not available:

    (define-condition machine-not-available-error (machine-error) ()
      (:report (lambda (condition stream)
                 (format stream "The machine ~A is not available."
                         (machine-error-machine-name condition)))))

We may now define a still more specific condition, built upon `machine-not-available-error`, that provides a default for `machine-name` but does not provide any new slots or report information. It just gives the `machine-name` slot a default initialization:

    (define-condition my-favorite-machine-not-available-error
                      (machine-not-available-error)
                      ((machine-name :initform "MC.LCS.MIT.EDU")))

Note that since no `:report` clause was given, the information inherited from `machine-not-available-error` will be used to report this type of condition.

Next: Creating Conditions Up: Program Interface to Previous: Handling Conditions