Common Lisp Condition System

CommonLisp Condition System is one of the best error handling system I have ever seen. The advantage of ConditionSystem? is the ability to separate the handling code from the decision code.

When a condition that should trigger an error is encounter you signal an event, providing choices of how to handle the error. Up in the higher level of the code, the condition handler is set, and when the signal is received, the handler get to choose the choice of action to do with that error.

For example, the code below.

 (define-condition on-zero-denominator (error)
   ((message :initarg :message :reader message)))

(defun high-level-code () (handler-bind ((on-zero-denominator #'(lambda (c) (format t "error signaled: ~a~%" (message c)) (invoke-restart 'return-zero))) (determine-infinity))

(handler-bind ((on-zero-denominator #'(lambda (c) (format t "error signaled: ~a~%" (message c)) (invoke-restart 'return-value 1)))) (determine-infinity))

(handler-bind ((on-zero-denominator #'(lambda (c) (format t "error signaled: ~a~%" (message c)) (invoke-restart 'recalc-using 2)))) (determine-infinity))

(handler-bind ((on-zero-denominator #'(lambda (c) (format t "error signaled: ~a~%" (message c)) (invoke-restart 'just-continue)))) (determine-infinity))

(format t "Done."))

(defun determine-infinity () (restart-case (let ((result 0)) (setf result (reciprocal-of 0)) (format t "Value: ~a~%" result)) (just-continue () nil)))

(defun reciprocal-of (value) (restart-case (if (/= value 0) (/ 1 value) (error 'on-zero-denominator :message "cannot use zero"))

(return-zero () 0) (return-value (r) r) (recalc-using (v) (reciprocal-of v))))
In high-level code, I decide what to do if an error of type ON-ZERO-DENOMINATOR occurs. The lower-level code (in the above example, RECIPROCAL-OF and DETERMINE-INFINITY) is the one that provides choices of actions.

This is one thing you can never do with normal exception handling: by the time you catch the exception, you have to know what to do with it. This is a contradiction in itself; the best place to know how to handle the error is usually nearest to the place where the error occurred, since that's where all information about the error is. However, the best place to decide what to do when an error occurs is usually higher in the call stack. The condition system provides both. You don't have to check every line of code for errors, yet you can still handle an error at the closest place to where it occurs.


I hope I haven't altered the semantics of the program above; I don't code in Lisp on a regular basis. However, the use of human-readable, yet meaningless, names along with the most bizarre indentation inconsistencies really threw me for a loop. Hopefully I've clarified the code to be easier to follow. Feel free to re-edit as appropriate.

International Standard Lisp Indentation is pretty much whatever Emacs spits out.


See LispRestartExample, AbortRetryIgnore, ResumableException


CategoryLisp CategoryCommonLisp


EditText of this page (last edited November 8, 2013) or FindPage with title or text search