This SRFI is currently in withdrawn status. Here
is an
explanation of each status that a SRFI can hold. To provide
input on this SRFI, please send email
to srfi-154@nospamsrfi.schemers.org
.
To subscribe to the list,
follow these
instructions. You can access previous messages via the
mailing
list archive.
dynamic-wind
, call-with-current-continuation
,
and parameterize
deal with the dynamic extent
indirectly. The same holds true for the procedures and syntaxes
dealing with continuation marks as defined
by SRFI
157.
This SRFI reifies the dynamic extent into a first-class value
together with a well-defined procedural interface and a syntax to
create procedures that remember not only their environment at
creation time but also their dynamic extent, which includes their
dynamic environment.
While the Scheme language is rich enough to reify the concept of the dynamic extent of a procedure — the sample implementation accompanying this SRFI is a proof of this fact — there is no standard for how such a reification is exposed to the user. It is the purpose of this SRFI to remedy this. In particular, Scheme systems may provide faster implementations than the portable sample implementation of this SRFI.
To give an example why explicit handling of dynamic extents can become necessary, let us consider the following two definitions:
(define (safe-sqrt error-handler) (lambda (x) (when (negative? x) (error-handler "negative argument")) (sqrt x))) (define (error-handler msg) (display msg (current-error-port)) (newline (current-error-port)) 'error) (define my-safe-sqrt (safe-sqrt error-handler))
When (my-safe-sqrt -1)
is executed, an error
message is printed on the current error port of the call
to my-safe-sqrt
. It may, however, have been the
intention of the user that the message is printed to whatever
error port was current when the
procedure error-handler
was defined. Using the
syntax of this SRFI, this can be achieved by changing the
definition of errror-handler
into:
(define error-handler (dynamic-lambda (msg) (display msg (current-error-port)) (newline (current-error-port)) 'error))
The same goes not only for error handlers, but for any other
type of callback procedure where it often makes sense to
reinstate the dynamic extent of the definition of the
callback for the dynamic extent of the call to the
callback, e.g. to define the callback
using dynamic-lambda
as opposed to using a
bare lambda
.
In case the error handler from the example above wants to call back on a provided continuation, we have to be a bit more careful so that the continuation is called in the expected dynamic extent:
(define (safe-sqrt error-handler) (lambda (x) (when (negative? x) (error-handler "negative argument" exit)) (sqrt x))) (define error-handler (let ((dynamic-extent (current-dynamic-extent))) (lambda (msg exit) (with-dynamic-extent dynamic-extent (lambda () (display msg (current-error-port)) (newline (current-error-port)))) (exit 'error)))) (define my-safe-sqrt (safe-sqrt error-handler))
Note that the example of the error handler above is meant as a
minimal example in order to demonstrate the capabilities of this
library. It is not an example that cannot easily be rewritten
in Scheme code not making use of this SRFI, e.g. by
taking a snapshot of the value
of current-error-port
in form of a lexical
variable. However, note that in general more than one parameter
object may be accessed in the dynamic extent of the body of
the dynamic-lambda
or that they are not necessarily
known. In the presence
of SRFI
39 parameter objects, which are mutable, the technique of
taking a snapshot of the current value won't work. Finally, the
dynamic extent can be much more complicated than just about
parameter objects. For example, there could be global state that
is set up and restored by dynamic-wind
. In all
these cases, this SRFI provides the cleanest solution.
Another use case
is SRFI
155, which provides a specification and an implementation of
promises such that lazy algorithms using them work well in the presence
of call-with-current-continuation
and dynamically
scoped variables.
Throughout, we reuse the term dynamic extent for a reified
dynamic extent. In this sense, the before
thunk of a call to dynamic-wind
sets up the dynamic extent of the call
to thunk
, while
the after
thunk restores the dynamic
extent of the call to dynamic-wind
.
Dynamic extents form a type not necessarily disjoint from other Scheme types.
An R7RS system implementing this SRFI shall export the
following procedures and the syntax under the library
name (srfi 154)
.
(dynamic-extent? obj
)
The dynamic-extent?
procedure
returns #t
if its argument is a dynamic extent,
and #f
otherwise. Note that dynamic extents are not
necessarily disjoint from other Scheme types such as procedures.
(current-dynamic-extent)
The current-dynamic-extent
procedure returns
the current dynamic extent by capturing the dynamic extent
of the call to current-dynamic-extent
, which
can be reinstated by the
procedure with-dynamic-extent
.
(with-dynamic-extent dynamic-extent thunk)
The with-dynamic-extent
procedure calls
the thunk
and returns the values yielded
by thunk
. The call
to thunk
happens in the dynamic extent
captured by the dynamic-extent
.
(dynamic-lambda <formals> <body>)
A dynamic-lambda
expression is equivalent
to lambda
expression except that not only the
environment but also the dynamic extent in effect when the
dynamic-lambda
expression was evaluated is
remembered.
In other words, the expression (dynamic-lambda
<formals> <body>)
closes also over the
dynamic extent.
Any Scheme system implementing both SRFI 154 and SRFI 157 shall also define the following procedures.
An R7RS system shall export these procedures under
the (srfi 157 key)
library name.
Note: It also makes sense for a Scheme system that only implements the continuation marks of SRFI 157 and not the first-class dynamic extents described in this document to implement the following procedures.
(make-continuation-mark-key)
The make-continuation-mark-key
procedure returns
an object that can be used as a key for continuation marks. The
returned object is guaranteed to be different (in the sense
of eq?
) from any other existing Scheme object
including other Scheme objects returned by previous or future
invocations of make-continuation-mark-key
.
(continuation-mark-key? obj)
The continuation-mark-key?
procedure
returns #t
if obj
is a Scheme
object that was returned by a previous invocation
of make-continuation-mark-key
, and #f
,
otherwise.
Rationale: Continuation marks can associate values with
arbitrary keys, not just keys created
by make-continuation-mark-key
. However,
continuation marks associated with keys created
by make-continuation-mark-key
can be more
efficient because the implementation is free to store
arbitrary metadata inside the key, which it can use to
implement continuation marks associated with these keys more
efficiently.
Therefore, it is generally recommended to use continuation
marks with these keys.
(make-continuation-mark-shallow-key)
The make-continuation-mark-shallow-key
procedure
returns an object that can be used as a key for continuation
marks, called a shallow key. The returned object is guaranteed to be different (in
the sense of eq?
) from any other existing Scheme
object including other Scheme objects returned by previous or
future invocations of make-continuation-mark-shallow-key
.
The set of continuation marks returned by the
procedure (current-continuation-marks)
(originally
defined in SRFI 157) retains not the full list of all
continuation mark values associated with a shallow key but this
list truncated after the first element. (For any other type of
key, the set of continuation marks contains the full list of all
continuation mark values.)
(continuation-mark-shallow-key? obj)
The continuation-mark-shallow-key?
procedure
returns #t
if obj
is a Scheme
object that was returned by a previous invocation
of make-continuation-mark-shallow-key
, and #f
,
otherwise.
Rationale: Often, user code is only interested in the latest
continuation mark value associated with a key, that is the
procedure continuation-mark-set-first
instead
of continuation-mark-set->list
is used.
(The implementation of parameter objects in SRFI 157 is an
example.) In conjunction with the first-class dynamic extents
of this SRFI, this can lead to space leaks
because current-dynamic-extent
generally has to
capture all continuation mark values and not only the latest.
For continuation marks associated with shallow keys,
however, current-dynamic-extent
only needs to
retain the latest values.
The sample implementation is provided
as an R7RS library. It should be easily adaptable to any Scheme
implementation as long as that implementation has a concept of
dynamic extents,
provides call-with-current-continuation
, and has a
macro facility (for implementing dynamic-lambda
).
The sample implementation includes a special algorithm for use with Chibi Scheme. This serves as a demonstration how efficient implementations of this specification can be provided by Scheme implementers.
A full implementation of this specification (including an
implementation of SRFI 157 and its extension described here) is
provided in the form of a meta-circular interpreter written in
R7RS dubbed Inferior Scheme,
which is a REPL for the Scheme IEEE 1178-1990 standard. Outside
of the standard syntax and procedures, Inferior Scheme provides
the syntax and the procedures of SRFIs 154, 155, and 157, the load
procedure of the R4RS, dynamic-wind
of R5RS,
and parameterize
and make-parameter
of
R7RS.
Thanks go to the SRFI editor for keeping the SRFI process alive and running. Special thanks go to Jim Rees for playing with and testing this proposed specification. Fruitful discussions with him led to the definition of shallow keys to prevent space leaks in SRFI 155 and other use cases.
Copyright (C) Marc Nieper-Wißkirchen (2017, 2018). All Rights Reserved.
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 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.