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.