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.
Condition definition
(define-condition evenp-error (error)
((text :initarg :text :reader text)))
Define function that prints odd number
(defun filter-evenp (lst)
(dolist (x lst)
(if (not (evenp x))
(print x)
(error 'evenp-error :text x))))
Restart function
(defun skip-evenp (c) (invoke-start 'skip-evenp))
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!!
In Common LISP terminology, exceptions are called conditions. In fact, conditions are more general than exceptions in traditional programming languages, because a condition represents any occurrence, error, or not, which might affect various levels of function call stack.
The condition system allows the program to deal with exceptional situations, or situations which are outside the normal operation of the program as defined by the programmer. A common example of an exceptional situation is an error, however the Common Lisp condition system encompasses much more than error handling.
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.
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.
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.
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)))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With