common lisp exception handling (condition and restart)
Asked Answered
L

2

7

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!!

Leifleifer answered 23/3, 2016 at 12:24 Comment(0)
F
6

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)))
Fultz answered 23/3, 2016 at 12:52 Comment(4)
thanks for your response, I am still not sure that where #'skip-evenp refer. to ?Leifleifer
#'SKIP-EVENP refers to the function by that name (that's what the #' means). The HANDLER-BIND tells it to handle any EVENP-ERRORs by calling the function SKIP-EVENP.Fultz
@Leifleifer #' is a reader-macro: the above is equivalent to (function skip-event) and uses the function special operator to refer to the function object lexically bound to the skip-event symbol.Maleki
@Fultz thanks very much, I finally figured it out after searched more sample via google,Leifleifer
M
7

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.

Maleki answered 23/3, 2016 at 12:56 Comment(2)
the solution you provide the exactly what I want, but I am still confused with the restart stuff, let me try to find some more material to figure it out, thanks!Leifleifer
@Leifleifer If the user is useful, consider accepting it with the green checkmark. I'll add more references if that can help you.Maleki
F
6

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)))
Fultz answered 23/3, 2016 at 12:52 Comment(4)
thanks for your response, I am still not sure that where #'skip-evenp refer. to ?Leifleifer
#'SKIP-EVENP refers to the function by that name (that's what the #' means). The HANDLER-BIND tells it to handle any EVENP-ERRORs by calling the function SKIP-EVENP.Fultz
@Leifleifer #' is a reader-macro: the above is equivalent to (function skip-event) and uses the function special operator to refer to the function object lexically bound to the skip-event symbol.Maleki
@Fultz thanks very much, I finally figured it out after searched more sample via google,Leifleifer

© 2022 - 2024 — McMap. All rights reserved.