Status: Version 2, Passed, Jun 89 X3J13Version 3, as amended & passed Nov 89 X3J13
Issue: CONDITION-RESTARTS
Forum: Cleanup
References: Common Lisp Condition System
Category: CHANGE
Edit history: 18-Jan-89, Version 1 by Pitman
26-Jun-89, Version 2 by Pitman and Barrett
3-Dec-89, Version 3 by Masinter (as amended Nov 89 X3J13)
Problem Description:
It was noted in the condition system document itself, and many people have
complained privately, that a major weakness of the condition system is the
inability to know whether a particular restart is associated with a
particular signalling action. Also, there has been an interest in
disabling an established restart in some situations.
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.
Proposal (CONDITION-RESTARTS:PERMIT-ASSOCIATION):
1. Define that during the dynamic extent of a call to 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
(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).
Rationale:
1. The ability to recycle a condition object (including the ability to
resignal a condition) means that the same condition object might be
simultaneously active for two different purposes. In such a case, no
test (not even 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.
Test Case:
(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.
(WITH-CONDITION-RESTARTS FOO (LIST (FIND-RESTART 'ALPHA))
(INVOKE-RESTART 'ALPHA)
(ALPHA () 2)))
(ALPHA () 1)))
=> 2
(LET ((FOO (MAKE-CONDITION 'SIMPLE-CONDITION)))
(WITH-CONDITION-RESTARTS FOO (LIST (FIND-RESTART 'ALPHA))
(INVOKE-RESTART (FIND-RESTART 'ALPHA FOO))
(ALPHA () 2)))
(ALPHA () 1)))
=> 1
(WITH-CONDITION-RESTARTS FOO (LIST (FIND-RESTART 'ALPHA))
(INVOKE-RESTART 'ALPHA)
(ALPHA () :TEST (LAMBDA (C) (DECLARE (IGNORE C)) NIL)
2)))
(ALPHA () 1)))
=> 1
Current Practice:
Presumably no implementation does this yet.
Cost to Implementors:
Several small, relatively modular changes.
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.
Cost to Users:
Except for the change to the recyclability of restarts, this change is
upward compatible.
Probably very few if any users currently take advantage of recycling
restarts, so the cost to users of this change is very slight.
Cost of Non-Adoption:
Use of restarts would not be nearly as reliable.
Benefits:
It would be possible to write code which was considerably more robust.
Aesthetics:
Some people might consider this proposal to make things slightly better
because it avoids some ambiguities. Others might consider it to make
things slightly worse because it adds additional complexity.
Discussion:
Pitman and Barrett support this proposal.