Wolfgang Corcoran-Mathe
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.
This SRFI was forked from SRFI 249 on that date.
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.
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 exception 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.
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.)
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.
A restarter is a condition object (see the R6RS
standard libraries document, Section 7.2) of type
&restarter
.
It has five fields:
#f
.The restarter condition type could be defined by
(define-condition-type &restarter &condition
make-restarter restarter?
(tag restarter-tag)
(description restarter-description)
(who restarter-who)
(formals restarter-formals)
(condition-predicate restarter-condition-predicate)
(invoker restarter-invoker))
A restarter’s fields are specific to that restarter, and must be preserved if the restarter is compounded with other condition objects and later extracted. Therefore compound conditions must not be used to implement these informational fields.
Usually, restarters and their handlers will be constructed implicitly by the high-level forms described in the Syntax section. Restarters and handlers that invoke them must obey the following protocol:
When a restarter is raised in response to an exception (called the triggering exception in this document), it must 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.
A handler must not invoke a restarter if the object raised by the triggering exception does not satisfy that restarter’s condition predicate.
(make-restarter
tag description
who formals condition-predicate
invoker)
→
Returns a restarter condition 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.
The condition returned by make-restarter
must not have
the condition types &who
or
&message
.
The following example defines a simple restartable version of the
/
procedure. If /
raises any condition, then
safe-/
compounds that condition with a
use-arguments
restarter and
re-raises it.
(This is not the recommended way of defining restartable procedures,
but is included as an example of building and installing restarters
“by hand”. define-restartable
provides a much more compact syntax for the same thing.)
(define (safe-/ x y)
(call/cc
(lambda (return)
(let ((val-restarter
(make-restarter 'use-arguments
"Apply procedure to new arguments."
'safe-/
'(x y)
condition?
(lambda (x y)
(return (safe-/ x y))))))
(with-exception-handler
(lambda (con)
(raise-continuable
(if ((restarter-condition-predicate val-restarter) con)
(condition con val-restarter)
con)))
(lambda () (/ x y)))))))
> (with-current-interactor
(lambda () (safe-/ 1 0)))
Restartable exception occurred.
Who: /
Message: undefined for 0
Irritants: (0)
(use-arguments x y) [safe-/]: Apply procedure to new arguments.
restart[0]> (use-arguments 8 2)
⇒ 4
(restarter?
obj)
→
Returns #t
if obj is a restarter and
#f
otherwise.
(restarter-tag
restarter)
→
(restarter-description
restarter)
→
(restarter-who
restarter)
→
(restarter-condition-predicate
restarter)
→
(restarter-formals
restarter)
→
Returns the tag / description / who / condition predicate / 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.
An interactor is a procedure that accepts a single (usually composite) restarter object. The interactor presents the restarters of this object to the user. The information presented should include the tag, description, who, and formals of each restarter, and may also include information from other components (e.g. messages and irritants) of the condition argument. The user then selects a restarter and provides any additional data which that restarter needs. Finally, the interactor invokes the chosen restarter.
The precise manner of interaction is unspecified. An interactor
may present the available restarters through a graphical menu, a
traditional command-line prompt, or something else. An interactor
may or may not evaluate (in the sense of eval
) any
Scheme data received from the user (e.g. arguments to the selected
restarter’s invoker).
(The interactive behavior of one possible interactor is presented in examples throughout this document. These examples are non-normative.)
current-interactor
A SRFI 39 / R7RS parameter object whose value is an interactor procedure.
(with-current-interactor
thunk)
Returns the results of invoking thunk. An
exception handler is installed for the dynamic extent (as determined by
dynamic-wind
) of the invocation of thunk. If this
handler is passed a condition with type &restarter
,
then it evaluates
(current-interactor)
and applies the result to that
condition. Non-restarter conditions are
re-raised with raise-continuable
. If the interactor returns,
then a non-continuable exception is raised.
Note that, since the current interactor is retrieved after an
exception occurs and not when with-current-interactor
is called, thunk may install new interactors dynamically.
In the following, for example, a custom interactor is used to
restart exceptions raised by one branch of the cond
form:
(define (foo obj)
(with-current-interactor
(lambda ()
(cond ((procedure? obj)
(parameterize ((current-interactor my-custom-interactor))
...)) ; use my-custom-interactor
(else ...))))) ; use the default interactor
(restarter-guard
who
restarter-clauses
body)
restarter-clauses takes one of two forms, either
(((
tag.
formals)
description pred-expression restarter-body) …)
or
(
condition-var((
tag.
formals)
description pred-expression restarter-body) …)
who must be an identifier or string. condition-var and tag are identifiers. formals is a formals list as specified in Section 4.1.4 of the R7RS. description is a string. pred-expression is an expression that must evaluate to a predicate (R7RS §6.1).
A syntax error is signaled if any tag appears in more than one clause.
First, the pred-expressions are evaluated
to condition predicates in an
unspecified order. Then, an exception handler
is installed for the dynamic extent (as
determined by dynamic-wind
) of the invocation of
body. If an exception occurs, this handler
constructs a compound restarter condition, which includes the condition
raised by the triggering exception and
restarters constructed from the restarter-clauses,
and raises it with raise-continuable
. Only restarters
whose condition predicates return true when applied to the raised
condition are included.
The who field of each restarter constructed from
restarter-clauses is initialized to
who, and
the condition-predicate field to the value of
pred-expression.
Each invoker procedure is constructed from a
formals and a restarter-body.
If condition-var was provided, then the condition
raised by the triggering exception is bound to it in
each restarter-body.
When an invoker is invoked, the results of evaluating
restarter-body are delivered to the continuation
of the restarter-guard
expression.
(define (safe-/ a b)
(restarter-guard safe-/
(con ((return-value v)
"Return a specific value."
assertion-violation?
v)
((return-numerator)
"Return the numerator."
assertion-violation?
a)
((return-zero)
"Return zero."
assertion-violation?
0))
(/ a b)))
> (with-current-interactor
(lambda () (safe-/ 1 0)))
Restartable exception occurred.
Who: /
Message: undefined for 0
Irritants: (0)
(return-value v) [safe-/]: Return a specific value.
(return-numerator) [safe-/]: Return the numerator.
(return-zero) [safe-/]: Return zero.
restart[0]> (return-numerator)
⇒ 1
The restartable
form wraps a procedure in code allowing
it to be re-invoked on new arguments if an assertion
violation occurs.
(restartable
who
expr)
who must be an identifier or string.
expr must evaluate to a procedure. The
restartable
form establishes (as if with
restarter-guard
) a
restarter with tag
use-arguments
for the
dynamic extent of this procedure’s invocation. If an assertion
violation (an exception raising a condition having type
&assertion
(see R6RS §7.3)) occurs during invocation,
the restarter accepts new formals and re-invokes the procedure on
these arguments. The who
field
of the restarter is filled by who.
>
(with-current-interactor (lambda () (map (restartable "divider" (lambda (x) (/ 10 x))) '(1 2 0 4))))Restartable exception occurred. Who: / Message: undefined for 0 Irritants: (0) (use-arguments . args) [divider]: Apply the procedure to new arguments. restart[0]>
(use-arguments 3)⇒ (10 5 10/3 5/2) ;; A 'map' procedure with two restarts. The first allows the ;; user to provide a list to return as the value of the whole ;; 'map-restartable' computation, while the second re-invokes ;; *proc* with new arguments. (define (map-restartable proc lis) (restarter-guard map-restartable (mcon ((use-list new-lis) "Return new-lis as the value of map-restartable." serious-condition? (assert (list? new-lis)) new-lis)) (map (restartable "[mapped procedure]" proc) lis))) >
(with-current-interactor (lambda () (map-restartable (lambda (x) (/ 10 x))) '(1 2 0 4))))Restartable exception occurred. Who: / Message: undefined for 0 Irritants: (0) (use-arguments . args) [[mapped procedure]]: Apply the procedure to new arguments. (use-list new-lis) [map-restartable]: Return new-lis as the value of map-restartable. restart[0]>
(use-list '(#f)) ⇒ (#f)>
(with-current-interactor (lambda () (map-restartable "divider" (lambda (x) (/ 10 x))) '(1 2 0 4))))Restartable exception occurred. Who: / Message: undefined for 0 Irritants: (0) (use-arguments . args) [divider]: Apply the procedure to new arguments. (use-list new-lis) [map-restartable]: Return new-lis as the value of map-restartable. restart[0]>
(use-arguments -1)⇒ (10 5 -10 5/2)
A restartable procedure definition takes one of the following forms:
(define-restartable (
variable
formals)
body)
(define-restartable (
variable
.
formal)
body)
The
define-restartable
form defines variable,
binding it to a new procedure. A restarter with tag
use-arguments
is established (as if with
restarter-guard
) for the
dynamic extent of this procedure's invocation. If an assertion
violation (an exception raising a condition having type
&assertion
(see R6RS §7.3)) occurs during invocation,
the restarter accepts new formals and re-invokes the procedure on
these arguments. The who
and formals
fields
of the restarter are filled by variable and
formals / formal,
respectively.
(define-restartable
variable
expr)
Shorthand for
(define
variable(restartable
variable expr)
(define-restartable (safe-/ x y) (/ x y)) >
(with-current-interactor (lambda () (safe-/ 4 0)))Restartable exception occurred. Who: / Message: undefined for 0 Irritants: (0) (use-arguments x y) [safe-/]: Apply the procedure to new arguments. restart[0]>
(use-arguments 4 2)⇒ 2
Users of SRFI 255 are encouraged to use the following tag protocol when naming their restarters.
abort
restarter accepts zero arguments.ignore
restarter accepts zero arguments.retry
restarter accepts zero arguments.use-arguments
restarter accepts zero
or more arguments. This is the tag protocol used by
restartable
and define-restartable
.The implementation is available at Github. It is in portable R6RS Scheme.
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.
Marc Nieper-Wißkirchen came up with the original concept and sample implementation of the SRFI 255 restarter system. His work provided the foundation for this SRFI and is gratefully acknowledged. (This is not meant to imply that he endorses the final SRFI.)
Thanks to John Cowan for his work on SRFI 249, and his tireless work on so much else in Scheme.
Thanks to those who provided reviews and commentary via the SRFI 255 mailing list or the #scheme IRC channel, including: Arthur A. Gleckler, Daphne Preston-Kendal, Vincent Manis, and Andrew Whatson.
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
© 2024 Taylor Campbell, John Cowan, Wolfgang Corcoran-Mathe, Marc Nieper-Wißkirchen, 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.