Common Lisp the Language, 2nd Edition
Next: Finding and
Manipulating Up: Program
Interface to Previous: Creating Conditions
The lowest-level form that creates restart points is called
restart-bind
. The restart-case
macro is an
abstraction that addresses many common needs for
restart-bind
while offering a more palatable syntax. See
also with-simple-restart
. The function that transfers
control to a restart point established by one of these macros is called
invoke-restart
.
All restarts have dynamic extent; a restart does not survive execution of the form that establishes it.
[Macro]
with-simple-restart (name format-string {format-argument}*)
{form}*
This is shorthand for one of the most common uses of
restart-case
.
If the restart designated by name is not invoked while
executing the forms, all values returned by the last
form are returned. If that restart is invoked, control is
transferred to the with-simple-restart
form, which
immediately returns the two values nil
and
t
.
The name may be nil
, in which case an anonymous
restart is established.
with-simple-restart
could be defined by
(defmacro with-simple-restart ((restart-name format-string
&rest format-arguments)
&body forms)
`(restart-case (progn ,@forms)
(,restart-name ()
:report
(lambda (stream)
(format stream ,format-string ,@format-arguments))
(values nil t))))
Here is an example of the use of
with-simple-restart
.
Lisp> (defun read-eval-print-loop (level)
(with-simple-restart
(abort "Exit command level ~D." level)
(loop
(with-simple-restart
(abort "Return to command level ~D." level)
(let ((form (prog2 (fresh-line)
(read)
(fresh-line))))
(prin1 (eval form)))))))
=> READ-EVAL-PRINT-LOOP
Lisp> (read-eval-print-loop 1)
(+ 'a 3)
Error: The argument, A, to the function + was of the wrong type.
The function expected a number.
To continue, type :CONTINUE followed by an option number:
1: Specify a value to use this time.
2: Return to command level 1.
3: Exit command level 1.
4: Return to Lisp Toplevel.
Debug>
Compatibility note: In contrast to the way that
Zetalisp has traditionally defined abort
as a kind of
condition to be handled, the Common Lisp Condition System defines
abort
as a way to restart (``proceed’’ in Zetalisp
terms).
Remark: Some readers may wonder what ought to be done by the ``abort’’ key (or whatever the implementation’s interrupt key is-Control-C or Control-G, for example). Such interrupts, whether synchronous or asynchronous in nature, are beyond the scope of this chapter and indeed are not currently addressed by Common Lisp at all. This may be a topic worth standardizing under separate cover. Here is some speculation about some possible things that might happen.
An implementation might simply call abort
or
break
directly without signaling any condition.
Another implementation might signal some condition related to the
fact that a key had been pressed rather than to the action that should
be taken. This is one way to allow user customization. Perhaps there
would be an implementation-dependent keyboard-interrupt
condition type with a slot containing the key that was pressed-or
perhaps there would be such a condition type, but rather than its having
slots, different subtypes of that type with names like
keyboard-abort
, keyboard-break
, and so on
might be signaled. That implementation would then document the action it
would take if user programs failed to handle the condition, and perhaps
ways for user programs to usefully dismiss the interrupt.
Implementation note: Implementors are encouraged to
make sure that there is always a restart named abort
around
any user code so that user code can call abort
at any time
and expect something reasonable to happen; exactly what the reasonable
thing is may vary somewhat. Typically, in an interactive program,
invoking abort
should return the user to top level, though
in some batch or multi-processing situations killing the running process
might be more appropriate.
[Macro]
restart-case expression {(case-name arglist
{keyword value}*
{form}*)}*
The expression is evaluated in a dynamic context where the
clauses have special meanings as points to which control may be
transferred. If the expression finishes executing and returns
any values, all such values are simply returned by the
restart-case
form. While the expression is
running, any code may transfer control to one of the clauses (see
invoke-restart
). If a transfer occurs, the forms
in the body of that clause will be evaluated and any values returned by
the last such form will be returned by the
restart-case
form.
As a special case, if the expression is a list whose
car is signal
, error
,
cerror
, or warn
, then
with-condition-restarts
is implicitly used to associate the
restarts with the condition to be signaled. For example,
(restart-case (signal weird-error)
(become-confused ...)
(rewind-line-printer ...)
(halt-and-catch-fire ...))
is equivalent to
(restart-case (with-condition-restarts
weird-error
(list (find-restart 'become-confused)
(find-restart 'rewind-line-printer)
(find-restart 'halt-and-catch-fire))
(signal weird-error))
(become-confused ...)
(rewind-line-printer ...)
(halt-and-catch-fire ...))
If there are no forms in a selected clause,
restart-case
returns nil
.
The case-name may be nil
or a symbol naming
this restart.
It is possible to have more than one clause use the same
case-name. In this case, the first clause with that name will
be found by find-restart
. The other clauses are accessible
using compute-restarts
. [In this respect,
restart-case
is rather different from
case
!-GLS]
Each arglist is a normal lambda-list containing parameters
to be bound during the execution of its corresponding forms.
These parameters are used to pass any necessary data from a call to
invoke-restart
to the restart-case
clause.
By default, invoke-restart-interactively
will pass no
arguments and all parameters must be optional in order to accommodate
interactive restarting. However, the parameters need not be optional if
the :interactive
keyword has been used to inform
invoke-restart-interactively
about how to compute a proper
argument list.
The valid keyword value pairs are the following:
:test
fn
The fn must be a suitable argument for the
function
special form. The expression
(function
fn
)
will be
evaluated in the current lexical environment. It should produce a
function of one argument, a condition. If this function returns
nil
when given some condition, functions such as
find-restart
, compute-restart
, and
invoke-restart
will not consider this restart when
searching for restarts associated with that condition. If this pair is
not supplied, it is as if
(lambda (c) (declare (ignore c)) t)
were used for the fn.
:interactive
fn
The fn must be a suitable argument for the
function
special form. The expression
(function
fn
)
will be
evaluated in the current lexical environment. It should produce a
function of no arguments that returns arguments to be used by
invoke-restart-interactively
when invoking this function.
This function will be called in the dynamic environment available prior
to any restart attempt. It may interact with the user on the stream in
*query-io*
.
If a restart is invoked interactively but no
:interactive
option was supplied, the argument list used in
the invocation is the empty list.
:report
exp
If exp is not a literal string, it must be a suitable argument
to the function
special form. The expression
(function
exp
)
will be
evaluated in the current lexical environment. It should produce a
function of one argument, a stream, that prints on the stream a
description of the restart. This function is called whenever the restart
is printed while *print-escape*
is nil
.
If exp is a literal string, it is shorthand for
(lambda (s) (write-string exp s))
[That is, a function is provided that will simply write the given string literally to the stream.-GLS]
If a named restart is asked to report but no report information has been supplied, the name of the restart is used in generating default report text.
When *print-escape*
is nil
, the printer
will use the report information for a restart. For example, a debugger
might announce the action of typing ``:continue
’’ by
executing the equivalent of
(format *debug-io* "~&~S - ~A~%" ':continue some-restart)
which might then display as something like
:CONTINUE - Return to command level.
It is an error if an unnamed restart is used and no report information is provided.
Rationale: Unnamed restarts are required to have report information on the grounds that they are generally only useful interactively, and an interactive option that has no description is of little value.
Implementation note: Implementations are encouraged to warn about this error at compilation time.
At run time, this error might be noticed when entering the debugger. Since signaling an error would probably cause recursive entry into the debugger (causing yet another recursive error, and so on), it is suggested that the debugger print some indication of such problems when they occur, but not actually signal errors.
Note that
(restart-case expression
(name1 arglist1 options1 . body1)
(name2 arglist2 options2 . body2)
...)
is essentially equivalent to
(block #1=#:block-1
(let ((#2=#:var-2 nil))
(tagbody
(restart-bind ((name1 #'(lambda (&rest temp)
(setq #2# temp)
(go #3=#:tag-3))
<slightly transformed options1>)
(name2 #'(lambda (&rest temp)
(setq #2# temp)
(go #4=#:tag-4))
<slightly transformed options2>)
...)
(return-from #1# expression))
#3# (return-from #1#
(apply #'(lambda arglist1 . body1) #2#))
#4# (return-from #1#
(apply #'(lambda arglist2 . body2) #2#))
...)))
[Note the use of ``gensyms’’ such as #:block-1
as block
names, variables, and tagbody
tags in this example, and the
use of #
n
=
and
#
n
#
read-macro syntax to
indicate that the very same gensym appears in multiple places.-GLS]
Here are some examples of the use of restart-case
.
(loop
(restart-case (return (apply function some-args))
(new-function (new-function)
:report "Use a different function."
:interactive
(lambda ()
(list (prompt-for 'function "Function: ")))
(setq function new-function))))
(loop
(restart-case (return (apply function some-args))
(nil (new-function)
:report "Use a different function."
:interactive
(lambda ()
(list (prompt-for 'function "Function: ")))
(setq function new-function))))
(restart-case (a-command-loop)
(return-from-command-level ()
:report
(lambda (s) ;Argument s is a stream
(format s "Return from command level ~D." level))
nil))
(loop
(restart-case (another-random-computation)
(continue () nil)))
The first and second examples are equivalent from the point of view of someone using the interactive debugger, but they differ in one important aspect for non-interactive handling. If a handler ``knows about’’ named restarts, as in, for example,
(when (find-restart 'new-function)
(invoke-restart 'new-function the-replacement))
then only the first example, and not the second, will have control
transferred to its correction clause, since only the first example uses
a restart named new-function
.
Here is a more complete example:
(let ((my-food 'milk)
(my-color 'greenish-blue))
(do ()
((not (bad-food-color-p my-food my-color)))
(restart-case (error 'bad-food-color
:food my-food :color my-color)
(use-food (new-food)
:report "Use another food."
(setq my-food new-food))
(use-color (new-color)
:report "Use another color."
(setq my-color new-color))))
;; We won't get to here until MY-FOOD
;; and MY-COLOR are compatible.
(list my-food my-color))
Assuming that use-food
and use-color
have
been defined as
(defun use-food (new-food)
(invoke-restart 'use-food new-food))
(defun use-color (new-color)
(invoke-restart 'use-color new-color))
a handler can then restart from the error in either of two ways. It may correct the color or correct the food. For example:
#'(lambda (c) ... (use-color 'white) ...) ;Corrects color
#'(lambda (c) ... (use-food 'cheese) ...) ;Corrects food
Here is an example using handler-bind
and
restart-case
that refers to a condition type
foo-error
, presumably defined elsewhere:
(handler-bind ((foo-error #'(lambda (ignore) (use-value 7))))
(restart-case (error 'foo-error)
(use-value (x) (* x x))))
=> 49
[Macro]
restart-bind ({(
name
function
{
keyword
value
}*)}*) {
form
}*
Executes a body of forms in a dynamic context where the given restart bindings are in effect.
Each name may be nil
to indicate an anonymous
restart, or some other symbol to indicate a named restart.
Each function is a form that should evaluate to a function
to be used to perform the restart. If invoked, this function may either
perform a non-local transfer of control or it may return normally. The
function may take whatever arguments the programmer feels are
appropriate; it will be invoked only if invoke-restart
is
used from a program, or if a user interactively asks the debugger to
invoke it. In the case of interactive invocation, the
:interactive-function
option is used.
The valid keyword value pairs are as follows:
:test-function
form
The form will be evaluated in the current lexical environment
and should return a function of one argument, a condition. If this
function returns nil
when given some condition, functions
such as find-restart
, compute-restart
, and
invoke-restart
will not consider this restart when
searching for restarts associated with that condition. If this pair is
not supplied, it is as if
#'(lambda (c) (declare (ignore c)) t)
were used for the form.
:interactive-function
form
The form will be evaluated in the current lexical environment
and should return a function of no arguments that constructs a list of
arguments to be used by invoke-restart-interactively
when
invoking this restart. The function may prompt interactively using
*query-io*
if necessary.
:report-function
form
The form will be evaluated in the current lexical environment
and should return a function of one argument, a stream, that prints on
the stream a summary of the action this restart will take. This function
is called whenever the restart is printed while
*print-escape*
is nil
.
[Macro]
with-condition-restarts condition-form restarts-form
{declaration}* {form}*
The value of condition-form should be a condition C
and the value of restarts-form should be a list of restarts
(
R1
R2
...)
.
The forms of the body are evaluated as an implicit
progn
. While in the dynamic context of the body, an attempt
to find a restart associated with a particular condition C’
will consider the restarts R1, R2, … if
C’ is eq
to C.
Usually this macro is not used explicitly in code, because
restart-case
handles most of the common uses in a way that
is syntactically more concise.
[The X3J13 vote (CONDITION-RESTARTS) left it unclear whether
with-condition-restarts
permits declarations to appear at
the heads of its body. I believe that was the intent, but this is only
my interpretation.-GLS]
Next: Finding and
Manipulating Up: Program
Interface to Previous: Creating Conditions
AI.Repository@cs.cmu.edu