common lisp exception handling (condition and restart)

3.3k Views Asked by At

I have been read the common lisp "Practical Common Lisp" exception handling chapter for days, but I am now so confused with the samples and the explanations, in the meanwhile I tried to write some testing sample, but it didn't work as I expected, below is my testing samples.

  1. Condition definition

    (define-condition evenp-error (error) 
      ((text :initarg :text :reader text)))
    
  2. Define function that prints odd number

    (defun filter-evenp (lst)
      (dolist (x lst)
        (if (not (evenp x)) 
          (print x)
          (error 'evenp-error :text x))))
    
  3. Restart function

    (defun skip-evenp (c) (invoke-start 'skip-evenp))
    
  4. Restart case

    (restart-case (filter-evenp (list 1 2 3 4 5))
      (skip-evenp () nil))
    

All I want to do is to print all odd numbers and skip the even errors, what is wrong with my sample? anybody help? many thanks in advance!!

2

There are 2 best solutions below

4
On BEST ANSWER

You need to put your RESTART-CASE to where you want to restart the execution at:

(defun filter-evenp (lst)
  (dolist (x lst)
    (restart-case
        (if (not (evenp x))
            (print x)
            (error 'evenp-error :text x))
      (skip-evenp () nil))))

Then you should use HANDLER-BIND to handle the error:

(handler-bind ((evenp-error #'skip-evenp))
  (filter-evenp (list 1 2 3 4 5)))
2
On

Practical Common Lisp is quite detailed but it is true that the condition system might require some time to get used to. You might be interested by Kent Pitman's articles: Exceptional Situations in Lisp and Condition Handling in the Lisp Language Family.

Those are referenced in What's a condition system and why do you want one?. There are also many other references lilke this Wikibooks entry or C2 wiki's CommonLispConditionSystem entry.

Defining a restart

A RESTART-CASE basically says:

I am going to execute this form and I don't care if it signals a condition or not. But if it does and you want to recover from that situation, here are different ways I can work around the problem (retry, ignore, etc.).

You generally cannot say how to recover from an error in the code being called at the call point. In other words, it is filter-evenp which should wrap the code with a restart-case in-order to provide alternative paths. For your example, it would be sufficient to use CERROR, which signals an error while establishing a CONTINUE restart.

(if (evenp x)
  (cerror "Ignore even number" 'evenp-error :text x) 
  (print x))

As an exercise you can try to replace (cerror ...) with an explicit restart-case construct.

Then, if you test your code, you should see your debugger pop up and show you the CONTINUE restart. If you defined your own restart, you can name it differently.

Invoking the restart

In your skip-evenp function, you were invoking a restart that was not established a at this point, and I think you were confused with skip-evenp naming both a restart and a function.

What you should do is handle the error by invoking the restart.

Here, you want the code that signals an error to continue, so you really don't want to unwind the execution stack. That's why you have to use HANDLER-BIND.

(handler-bind ((evenp-error (lambda (e) (invoke-restart 'continue))))
  (filter-evenp '(1 2 3 4)))
1
3    

You can of course extract the anonymous lambda into a custom function, like you did.