Common Lisp the Language, 2nd Edition


next up previous contents index
Next: Finding and Manipulating Up: Program Interface to Previous: Creating Conditions

29.4.7. Establishing Restarts

change_begin
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]
change_end



next up previous contents index
Next: Finding and Manipulating Up: Program Interface to Previous: Creating Conditions


AI.Repository@cs.cmu.edu