CLHS: Issue CONDITION-RESTARTS Writeup

#

## Issue CONDITION-RESTARTS Writeup

    Status:	Version 2, Passed, Jun 89 X3J13
    	Version 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
            (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).

    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.

      (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

    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.

Copyright 1996-2005, LispWorks Ltd. All rights reserved.