It's been a while since I have posted anything, mostly due to the fact that I'm teaching an undergraduate course on programming languages. Since today is a new holiday (ok, yesterday, technically) in my province ("Family Day"), I actually have a little free time. That hasn't happened in a while.
Lately, I've been working on an approach to dealing with certain kinds of errors such that evaluation continues, even though it may not be entirely correct. This has me working with one of my favourite features of Lisp or any other language: restarts. Restarts are not only a wonder to behold, but provide a lot of potential. The primary reason, in my opinion, is that you can continue computation in the face of some problem that doesn't have an immediately obvious solution.
That being said, the usefulness of restarts is limited by those who provide them. In your own code, you can add restarts as you see fit, but if you want to try and recover from problems indicated by the Lisp implementation, you're at the mercy of the Lisp implementor.
What restarts are made available and when are rarely (if ever) defined in the standard. So I thought it might be worthwhile to provide a partial survey of situations and whether an implementation provides the
continue restart (or something similar, such as
retry) so that you can correct the situation and continue, either interactively or within a program.
|No function defined1||✓||✓||-||✓|
|No class found||✓||✓||-||-|
|Division by zero||-||✓||-||-|
|No method found3||✓||✓||-||✓|
|No slot found3||✓||-||-||-|
|Replace function with generic function||✓||✓||✓||-|
|Redefine a generic function4||✓||✓||-||-|
0 The versions tested were ACL 8.1 Enterprise, Lispworks 5.0 Personal, SBCL 188.8.131.52, and CLISP 2.4.
1 Use case: evaluate a form such as
(foo 0) where
foo is not
(function foo) where
foo is not
3 In these situations, it is not necessary to offer a continuable restart, since there are ways to deal with this using the MOP.
4 Use case: define a generic function, then evaluate some
defmethod forms and make an incompatible change to the generic function definition using either
defmethod. Alternatively, define a method without a generic function definition, then make an incompatible change to generic function.
If an implementation doesn't offer a restart for
SETF functions (which usually means it doesn't offer one for
function lookup), you can add your own by customizing
*macroexpand-hook* to wrap the expansion in something that provides a way to continue, perhaps like the following, assuming that the
(setf (foo (list 1)) 2) is replaced with the actual expanded form.
(block #1=#:BLOCK-1000 (tagbody #2=#:START-1001 (handler-bind ((undefined-function #'(lambda (#3=#:CONDITION-1002) (cerror (format nil "Try calling ~A again." (cell-error-name #3#)) "~A is not fbound." (cell-error-name #3#)) (go #2#)))) (return-from #1# (setf (foo (list 1)) 2)))))
Doing this for everything that is macroexpanded would be a bit much, so you should probably only do it in certain circumstances.
Currently, these are the only situations I'm concerned with. Well, almost — I threw in the division by zero one since it's a canonical example of an error.
It's hard not to notice that the commercial implementations listed offer a lot more in the way of restarts than the free ones listed. I'm sure there is a reason for this, probably related to customer demands. I will say that the restarts offered by Allegro and LispWorks have caused me to start using them instead of SBCL. Doing what I want to do in SBCL requires a lot more hackery (either changing SBCL itself or using some techniques I'd rather not use). Right now, I'm using Allegro CL Enterprise since I got an equipment grant for it with AllegroGraph, but LispWorks is looking pretty good too.