255: Restarting conditions

Wolfgang Corcoran-Mathe (co-author and revised implementation), and Marc Nieper-Wißkirchen (co-author)

Status

This SRFI is currently in draft status. Here is an explanation of each status that a SRFI can hold. To provide input on this SRFI, please send email to srfi-255@nospamsrfi.schemers.org. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.

Table of Contents

  1. Abstract
  2. Issues
  3. Rationale
  4. Specification
    1. Procedures
    2. Parameter objects
    3. Syntax
    4. Proposed standard restart tags
  5. Implementation
  6. Acknowledgements
  7. References
  8. Copyright

Abstract

When an exceptional situation is encountered by a program, it usually creates a condition object describing the situation and then passes control to an exception handler. The signaler and handler are two different parts of a system, between which there is a barrier of abstraction. In order to recover gracefully and flexibly from exceptional situations, however, the signaler can provide multiple ways by which the handler can restart the computation, some of which may require extra input. Often, the decision of which method of recovery to choose is left up to a human user, who may be prompted for the input needed to recover. This SRFI proposes a mechanism called restarters which uses a new type of condition object and an associated exception handler to encapsulate the information necessary to restart a computation. We also describe the behavior and interface of interactor procedures, which implement interactive restarts.

Issues

Should we provide make-restarter? Hand-crafted restarters aren’t guaranteed to honor the “no return” contract.

Should restarter-guard provide a way to control which kinds of conditions can be restarted? Currently, restarter-guard is specified to restart any exception that occurs.

Should the evaluation environment of an interactor be specified?

Rationale

An effective and flexible system for gracefully handling and recovering from exceptional situations is a necessity for any large software system. While the continuable exceptions and guard form described in the R6RS and R7RS reports provide a basic kind of recovery system, they do not make it convenient for a signaling process to offer a choice of recovery routes to the handler. This SRFI attempts to make up for this deficiency by extending the handler-condition system with the forms and mechanisms needed for convenient recovery.

One important feature for a restart system to provide is interactivity. Though purely programmatic condition recovery is useful, it is well acknowledged by the designers and users of Common Lisp’s condition system that the ability to interactively choose a method of recovery for a condition is useful. This ability, built into the Common Lisp language, is one of the primary reasons for the power of Common Lisp development and debugging environments. In this SRFI, the interactivity is provided by an interactor procedure that by default is provided by the implementation, but can be overridden by the user.

SRFI 249

An earlier attempt at a restart system was made by the authors of SRFI 249, on which the current SRFI was originally based. The SRFI 249 system is very simple--indeed, it is less a restart system than a “build-your-own-restart-system” kit, since it requires careful programming to create correct restarters with the low-level tools that SRFI 249 provides. In contrast, SRFI 255 provides a high-level interface that automatically handles the messy control details. (Restarters can still be constructed “by hand”, if need be.)

Since the bulk of the SRFI 249 system also predates the exception systems of modern Scheme, it also duplicates the functionality of Scheme’s exception handlers by introducing a “restarter stack”. This stack, as a SRFI 39 / R7RS parameter object, is made part of the dynamic context of a Scheme program. Since we use the native exception system to install and manage restarters, there is no need for this complication.

Specification

Notation

The words “must”, “may”, etc., though not capitalized in this SRFI, are to be interpreted as described in RFC 2119.

The naming conventions of this document are similar to those used in the Scheme Reports: names such as symbol, obj, etc. have the type implications described in R7RS Section 1.3.3. In addition, an object named restarter must be a SRFI 255 restarter.

Restarter conditions

A restarter is a condition object (see the R6RS standard libraries document, Section 7.2) of type &restarter. It has five fields:

tag
A symbol describing this restarter.
description
A string that describes the method of recovery and the values, if any, needed for recovery.
who
A string or symbol identifying the entity reporting the exception that triggered the raising of the restarter condition.
formals
A symbol, list, or improper formals list as described in Section 4.1.4 of R7RS Small, describing the arguments expected by the restarter’s invoker.
invoker
A procedure that actually performs the recovery. It must not return. The number of arguments it expects is given by formals.

Restarters are usually constructed on the fly and established: an exception handler is installed that makes all currently established restarters available whenever an exception is raised.

When a restarter is raised in response to an exception (called the triggering exception in this document), it should be a composite condition (see Section 7.2.1 of the R6RS) including the object raised by the triggering exception as one of its components.

Procedures

(make-restarter tag description who formals invoker)restarter

Returns a restarter with the specified fields. The arguments of make-restarter must conform to the above specification of restarter fields. In particular, the invoker procedure must not return to its caller.

(restarter? obj)boolean

Returns #t if obj is a restarter and #f otherwise.

(restarter-tag restarter)symbol
(restarter-description restarter)string
(restarter-who restarter)symbol-or-string
(restarter-formals restarter)pair-or-symbol

Returns the tag / description / who / formals of restarter. The effect of mutating the values returned by these procedures is undefined.

(restart restarter arg) → does not return

Invokes the invoker procedure of restarter on the args.

If restarter’s invoker returns, then the result is undefined.

(with-abort-restarter thunk)

Establishes a restarter with tag abort for the dynamic extent of thunk (as determined by dynamic-wind). The abort restarter accepts zero arguments. When invoked, execution resumes with the continuation and dynamic environment of the with-abort-restarter form.

Interactors

An interactor is a procedure of one argument, a list of restarters. The interactor presents the information fields of those restarters to the user, who then picks a tag and provides a list of expressions. Those expressions are then evaluated in an unspecified environment and restart is applied to the chosen restarter and the user-provided values.

If an interactor returns, a non-continuable exception [TODO: of what type?] is raised.

(with-interactor interactor thunk)

Returns the results of invoking thunk. A new current exception handler is installed for the dynamic extent (as determined by dynamic-wind) of the invocation of thunk. If a restartable condition is raised in this extent, the handler invokes interactor on the list of available restarters provided by that condition.

(with-current-interactor thunk)

Equivalent to

(with-interactor (current-interactor) thunk)

Parameter objects

current-interactor

A SRFI 39 / R7RS parameter object whose value is an interactor procedure.

Syntax

(restarter-guard restarter-clauses body)

(restarter-guard who restarter-clauses body)

Syntax:

restarter-clauses takes one of two forms, either

(((tag . formals)
  description
  expression1 expression2) …)

or

(condition-var
 ((tag . formals)
  description
  expression1 expression2) …)

who should be an identifier or string. condition-var and tag tag are identifiers. formals is a formals list as specified in Section 4.1.4 of the R7RS. description is a string.

Semantics:

Installs a new exception handler for the dynamic extent (as determined by dynamic-wind) of the invocation of body. If an exception occurs, this handler constructs a restarter condition from the raised condition and from restarters constructed from the restarter-clauses, and raises it with raise-continuable. If condition-var is provided, then the condition object raised by the original triggering exception is bound to it in the environment of this handler.

The who field of each restarter constructed from restarter-clauses is filled by who, or #f if who was omitted. Each invoker is constructed from the expressions; when it is invoked, the values of the final expression are delivered to the continuation of the restarter-guard form.

Example:
(define (safe-/ a b)
  (restarter-guard safe-/
    (con ((return-value v)
	  "Return a specific value."
	  v)
	 ((return-numerator)
          "Return the numerator."
	  a)
	 ((return-zero)
	  "Return zero."
          0))
    (/ a b)))

> (with-current-interactor (lambda () (safe-/ 1 0)))

Restartable exception occurred.
(return-value v) [safe-/]: Return a specific value.
(return-numerator) [safe-/]: Return the numerator.
(return-zero) [safe-/]: Return zero.
restart[0]> (return-numerator)

  ⇒ 1

Restartable procedure syntax

A restartable form wraps a procedure in code allowing the procedure to be re-invoked on new arguments if an assertion violation occurs. It takes one of the following forms:

Examples:
(restartable
  define (safe-/ x y)
    (/ x y))

> (with-current-interactor (lambda () (safe-/ 4 0)))

Restartable exception occurred.
(use-arguments x y) [safe-/]: Apply the procedure to new arguments.
restart[0]> (replace-arguments 4 2)

  ⇒ 2

> (with-current-interactor
    (lambda ()
      (map (restartable "divider" (lambda (x) (/ 10 x)))
	   '(1 2 0 4))))

Restartable exception occurred.
(replace-arguments . args) [divider]: Apply the procedure to new arguments.
restart[0]> (replace-arguments 3)

  ⇒ (10 5 10/3 5/2)

Proposed standard restart tags

Users of SRFI 255 are encouraged to use the following tag protocol when naming their restarters.

abort
Completely aborts the computation. The invoker of an abort restarter accepts zero arguments.
ignore
Ignores the condition and proceeds. The invoker of an ignore restarter accepts zero arguments.
retry
Simply retries a whole computation from a certain point, with no explicitly altered inputs. Some implicit environmental changes are expected to have taken place. The invoker of a retry restarter accepts zero arguments.
use-arguments
Retries the invocation of some procedure, using new arguments. The invoker of a use-arguments restarter accepts zero or more arguments. This is the tag protocol used by restartable.

Implementation

The implementation is available at Github. It is in portable R6RS Scheme, and should be easy to port to other Scheme implementations with similar exception and condition systems.

Acknowledgements

This SRFI is based on John Cowan’s SRFI 249, which was itself based on a proposal by Taylor Campbell. The SRFI 255 “fork” was almost completely rewritten by Wolfgang Corcoran-Mathe and Marc Nieper-Wißkirchen.

The sample implementation was written by Marc Nieper-Wißkirchen and Wolfgang Corcoran-Mathe. The SRFI 249 sample implementation (on which the current implementation is not based) was written by Arvydas Silanskas and revised by Wolfgang Corcoran-Mathe.

Thanks to John Cowan for his work on SRFI 249, and his tireless work on so much else in Scheme.

References

Alex Shinn, John Cowan, & Arthur A. Gleckler, eds., Revised7 Report on the Algorithmic Language Scheme (R7RS Small) (2013). Available on the Web.

Michael Sperber, R. Kent Dybvig, Matthew Flatt, & Anton van Straaten, eds., The Revised6 Report on the Algorithmic Language Scheme (R6RS). Cambridge University Press, 2010. Available on the Web.

S. Bradner, Key words for use in RFCs to Indicate Requirement Levels. (RFC 2119) (1997). https://datatracker.ietf.org/doc/html/rfc2119

© 2023 Taylor Campbell, John Cowan, Wolfgang Corcoran-Mathe), Arvydas Silanskas.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


Editor: Arthur A. Gleckler