Title

First-class dynamic extents

Author

Marc Nieper-Wißkirchen

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

Issues

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  
    (closed-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 closed-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 capabalities 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 closed-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 setup 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.

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

(closed-lambda <formals> <body>)

A closed-lambda expression is equivalent to lambda expression except that not only the environment but also the dynamic extent in effect when the closed-lambda expression was evaluated is remembered. In other words, the expression (closed-lambda <formals> <body>) closes also over the dynamic extent.

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

Acknowledgements

Thanks go to the SRFI editor for keeping the SRFI process alive and running..

Copyright

Copyright (C) Marc Nieper-Wißkirchen (2017). 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