CONDITION-RESTARTS
The problem being addressed shows itself in situations involving recursive errors. The programmer wants to make sure that a restart obtained from FIND-RESTART or COMPUTE-RESTARTS is in fact present for the purpose of handling some particular error that he is actively focussed on, and not for some other (outer) error which he was not actively trying to handle.
Also, some implementors have wondered whether it was appropriate to implement restarts as objects with dynamic extent. It would be good to make this clear so that users are not surprised when porting programs.
SIGNAL with a particular condition, the effect of calling SIGNAL again on that condition object for a distinct abstract event is not defined.
For example, although a handler MAY resignal a condition in order to allow outer handlers first shot at handling the condition, two distinct asynchronous keyboard events may not signal an EQ condition object at the same time.
2. Define that restarts have dynamic extent.
3. Introduce a macro WITH-CONDITION-RESTARTS which can be used to dynamically bind the association between a condition and a set of restarts.
WITH-CONDITION-RESTARTS condition-form restarts-form &BODY forms [Macro]
Evaluates CONDITION-FORM and RESTARTS-FORM, the results of which should be a condition C and a list of restarts (R1 R2 ...), respectively. Then evaluates the body of forms in implicit-progn style, returning the result of the last form, or NIL if there are no forms. While in the dynamic context of the body, an attempt to find a restart Ri which is associated with a condition Ci (see #4 below), as in (FIND-RESTART rI cI), will consider the restarts R1, R2, etc. if (EQ CI C).
Usually this macro is not used explicitly in code, since RESTART-CASE handles most of the common cases in a way that is syntactically more concise.
4. Extend COMPUTE-RESTARTS, FIND-RESTART, ABORT, CONTINUE, USE-VALUE, and STORE-VALUE to permit an optional condition object as an argument.
When the extra argument is not supplied, these functions behave exactly as defined before. (Restarts are considered without prejudice to whether they have been associated with conditions.)
When this argument is supplied, only those restarts which are associated with the given condition or restarts which are not associated with any condition are considered. In all other respects, the behavior is the same. [However, see #7 for related developments.]
Passing a condition argument of NIL is treated the same as passing no condition argument.
5. Extend the definition of RESTART-CASE to say that if the form to do the signalling is a list whose car is any of SIGNAL, ERROR, CERROR, or WARN (or is a form which macroexpands into such a list), then WITH-CONDITION-RESTARTS is implicitly intended to associate the restarts with the condition to be signalled.
That is, for example,
(RESTART-CASE (SIGNAL FRED)
(A ...)
(B ...))
is equivalent to
(RESTART-CASE
(WITH-CONDITION-RESTARTS FRED
(LIST (FIND-RESTART 'A)
(FIND-RESTART 'B))
(SIGNAL FRED))
(A ...)
(B ...))
6. Define that Common Lisp macros such as CHECK-TYPE, which are defined to signal and to make restarts available, use the equivalent of WITH-CONDITION-RESTARTS to associate the conditions they signal with the defined restarts, so that users can make reliable tests not only for the restarts being available, but also for them being available for the right reasons.
7. Add a :TEST keyword to RESTART-CASE clauses and a :TEST-FUNCTION keyword to RESTART-BIND clauses (in the same general style as the :INTERACTIVE and :INTERACTIVE-FUNCTION keywords). These permit association of a function of one argument with a restart such that if this function returns false, then functions such as FIND-RESTART, COMPUTE-RESTARTS, and INVOKE-RESTART [see #4 above] will not consider the restart to be active. The argument to the test function is the value of the optional condition argument most of these functions accept (or nil for invoke-restart, since it doesn't have that argument). The default test function is
#'(LAMBDA (C) (DECLARE (IGNORE C)) T).
EQ) would suffice to determine whether a particular restart belonged with a particular signalling action, since the condition could not uniquely identify the signalling action. By making the programmer responsible for assuring that that a given condition may not be concurrently signalled for two conceptually distinct purposes, this sticky area is avoided.
2. There are no known reasons for restarts to not have dynamic extent. Important optimizations may be possible by allowing implementors this flexibility.
3. This is is the minimal level of support needed to set up an association between restarts and conditions.
4. This provides a natural interface for retrieving and using the information about the associations between conditions and restarts.
5. This provides a natural interface for the most common case of wanting to signal a restart with some associated conditions.
6. This is a logical consequence of 5.
7. Programmers and implementors have asked for a way of "filtering" restarts so that they are not visible in some contexts.
HANDLER-BIND ((ERROR #'(LAMBDA (C) (SIGNAL C) ...))) (SIGNAL "Test")) was permissible and continues to be. However, (HANDLER-BIND ((ERROR #'(LAMBDA (C) (SIGNAL FRED) ...))) (SIGNAL FRED)) may be undefined (depending on the programmer's intent) because the two uses of FRED are not cooperating and may not represent conceptually distinct situations.
(RESTART-CASE
(WITH-CONDITION-RESTARTS FOO (LIST (FIND-RESTART 'ALPHA))
(RESTART-CASE
(INVOKE-RESTART 'ALPHA)
(ALPHA () 2)))
(ALPHA () 1)))
=> 2
(LET ((FOO (MAKE-CONDITION 'SIMPLE-CONDITION)))
(RESTART-CASE
(WITH-CONDITION-RESTARTS FOO (LIST (FIND-RESTART 'ALPHA))
(RESTART-CASE
(INVOKE-RESTART (FIND-RESTART 'ALPHA FOO))
(ALPHA () 2)))
(ALPHA () 1)))
=> 1
(RESTART-CASE
(WITH-CONDITION-RESTARTS FOO (LIST (FIND-RESTART 'ALPHA))
(RESTART-CASE
(INVOKE-RESTART 'ALPHA)
(ALPHA () :TEST (LAMBDA (C) (DECLARE (IGNORE C)) NIL)
2)))
(ALPHA () 1)))
=> 1
The specific details of how the association is made between conditions and restarts is left up to the implementation. It is recommended (e.g., for the sake of the garbage collector) that the association be external to the condition, however, since the condition might persist for long afterward and the restart information has no value beyond the dynamic extent during which the actual signalling is occurring.
Probably very few if any users currently take advantage of recycling restarts, so the cost to users of this change is very slight.