Last call for comments on SRFI 248: Minimal delimited continuations
Arthur A. Gleckler
(28 Oct 2024 22:19 UTC)
|
Re: Last call for comments on SRFI 248: Minimal delimited continuations
Philip McGrath
(29 Oct 2024 00:30 UTC)
|
Re: Last call for comments on SRFI 248: Minimal delimited continuations Marc Nieper-Wißkirchen (29 Oct 2024 12:49 UTC)
|
Re: Last call for comments on SRFI 248: Minimal delimited continuations
Marc Nieper-Wißkirchen
(29 Oct 2024 14:01 UTC)
|
Re: Last call for comments on SRFI 248: Minimal delimited continuations
Marc Nieper-Wißkirchen
(29 Oct 2024 15:07 UTC)
|
Hi Philip, Thank you very much for your valuable input. Am Di., 29. Okt. 2024 um 01:30 Uhr schrieb Philip McGrath <xxxxxx@philipmcgrath.com>: > > Hi, > > On Mon, Oct 28, 2024, at 6:19 PM, Arthur A. Gleckler wrote: > > Marc Nieper-Wißkirchen, author of SRFI 248: Minimal delimited continuations, has agreed to announcing a last call for this SRFI. He believes that it is ready for finalization, but would like to get corrections and feedback before we finalize it. Feedback from Scheme implementers and maintainers is particularly welcome. > > > I have briefly looked at the document, sample implementation, and discussion. > > One immediate question jumps out at me: what is the purpose of `empty-continuation?`? It doesn't seem to be implemented by the sample implementation or used in the test suite. Is it a necessary part of this SRFI? If an implementation provides built-in delimited continuations but doesn't provide `empty-continuation?` as a primitive, it seems like it might be difficult or inefficient to emulate. It is not implemented in the sample implementation because it is not yet supported by Guile. It can easily be provided if the implementation supports continuation marks (using, e.g., Racket's call-with-immediate-continuation-mark) or in implementations where eqv? on continuation procedures is meaningful (e.g. as in Chez Scheme). Such a primitive is necessary to prevent space leaks: consider, for example, a program that keeps track of a "metacontinuation" as a list of delimited continuation procedures k. See also at the bottom of this post. > I also want to clarify the semantics of `with-unwind-handler`. The specification says that the anonymous exception handler it installs "applies *handler* on *obj* and *k* in the continuation and dynamic environment of the call to `with-unwind-handler`." That seems to mean that the anonymous handler aborts to the prompt before applying *handler* (though after capturing *k*)—and indeed, that's what the the sample implementation seems to do. But it is subtly different than the R6RS `with-exception-handler`, where the handler in called in the dynamic environment of the call to `raise`, "except that the current exception handler is the one that was in place when the handler being called was installed" (plus details about the handler returning that differ for `raise` and `raise-continuable`). Indeed, and that's one reason for the name part "unwind"; the stack is unwound up to the point of the installation of the handler. Copying the semantics of 'with-exception-handler' would necessitate the use of call/cc in virtually all scenarios and would lead to inefficient coding idioms. I will point out this difference in the final draft. > (As an aside, I would love some clearer way to distinguish "handler" from the "anonymous handler.") I could give the latter a name, say, A, and refer to it only by A. (In any case, we have two types of handlers in this context, namely handlers for 'with-unwind-handler' and handlers for 'with-exception-handler'). > Based on the note in https://srfi-email.schemers.org/srfi-248/msg/25615855/, it sounds like the semantics should also clarify that *k* is a composable continuation. I can add a note about the fact that K is a procedure that returns and thus represents a composable continuation. > Similarly, I'd like more precision about "the portion of the current continuation up to and including the call to `with-unwind-handler`". The use of "including" is confusingly similar to (but IIUC distinct from) the notion of continuation capture (not) including a prompt as in https://www-old.cs.utah.edu/plt/publications/icfp07-fyff.pdf: > > With respect to the many composable control operators in the > literature, we have opted for a variant where capture does not > include the delimiting prompt, and composition does not introduce > a prompt. Previous work explores the design space related to this > choice in depth (Shan 2004; Kiselyov 2005a; Biernacki et al. 2006; > Dybvig et al. 2006); when taking into account space complexity, > only the design that does not include or add a prompt is known to > express the others (Dybvig et al. 2006). As described in the abstract, SRFI 248 realises the shift0/reset0 forms. Their semantics are (see also Racket's documentation): (reset0 val) => val (reset0 E[(shift0 k expr)]) => ((lambda (k) expr) (lambda (v) (reset0 E[v]))) Here, E does not include 'reset0'. In plain English, in this variant, therefore, the capture K includes the delimiting prompt, but "composition does not introduce a prompt". Rewritten in terms of "with-unwind-handler", this means: (with-unwind-handler handler (lambda () val)) => val (with-unwind-handler handler (lambda () E[(raise-continuable obj)]) => (handler obj (lambda (v) (with-unwind-handler handler (lambda () E[v])))) In other terms, SRFI 248 implements the -F+ semantics described in [1]. Many in the algebraic effects community call a handler with such a semantic a "deep handler", while the prompt0/control0 or -F- semantics would correspond to a "shallow handler". If the delimited continuation should not be guarded by a handler, one can use a transcription of, say, (guard (c k (else H)) E) to (app-second (guard (c k^ (else (let ((k (lambda (x) (app-first (k^ x))))) (values (lambda () (k (raise-continuable c))) (lambda () H))))) (let ((v E)) (values (lambda () v) (lambda () v))))) Here, app-first and app-second are special forms that expect their argument to evaluate to two values, both thunks. app-first invokes the first, app-second the second. (The code snippet does not handle user continuations with multiple values for simplicity.) Note that when K^ is the empty continuation in the sense of SRFI 248, the code above builds unnecessary objects. A version without a space leak is thus: (app-second (guard (c k^ (else (let ((k (if (empty-continuation? k^) k^ (lambda (x) (app-first (k^ x)))))) (values (lambda () (k (raise-continuable c))) (lambda () H))))) (let ((v E)) (values (lambda () v) (lambda () v))))) I hope this helps, Marc [1] Dybvig et al.: A Monadic Framework for Delimited Continuations > > > Hope this is useful, > Philip