Title

First-class dynamic extents

Author

Marc Nieper-Wißkirchen

Status

This SRFI is currently in final 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.

Abstract

Scheme has the notion of the dynamic extent of a procedure call. A number of standard Scheme procedures and syntaxes like 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.

Rationale

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.

Specification

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).

Procedures

(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.

Syntax

(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.

Addendum to SRFI 157

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.

Implementation

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.

Acknowledgements

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

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.


Editor: Arthur A. Gleckler