Feature request: guardian-try-collect
Daphne Preston-Kendal 30 Nov 2023 23:33 UTC
I would like to propose an additional procedure, guardian-try-collect. When called on a guardian, it instructs the garbage collector to do as much collection as might be required to enqueue one object in the guardian for finalization.
Rationale: It is surprising even to experienced Schemers that finalization is the only safe means of managing resources other than memory. Common Lisp/Emacs Lisp programmers are used to having unwind-protect, and those coming from other languages may know try..finally. Scheme’s dynamic-wind appears temptingly similar, but the existence of first-class continuations means the exit thunk of a dynamic-wind is *not* the same as a finally block; a computation inside dynamic-wind which is returned to will not have access to its resources any more if dynamic-wind is used naively in this way.
SRFI 226 proposes a variant of unwind-protect which uses continuation barriers to forbid re-entering a computation after it has been exited once. However, this is in general too strong and reduces the composability of. For example, the following is not possible:
(define (file-lines-generator f)
(make-coroutine-generator
(lambda (yield)
(let ((ip (open-input-file f)))
(unwind-protect
(let loop ()
(let ((line (read-line f)))
(unless (eof-object? line)
(yield line)
(loop))))
;; cleanup:
(close-port ip))))))
because unwind-protect will close the port and prevent re-entry into the coroutine generator after the first line is yielded.
Taylor Campbell shows how to implement unwind-protect correctly for Scheme using finalization: <http://mumble.net/~campbell/tmp/unwind-protect-opt.scm>
In terms of guardians with guardian-try-collect, this can be expressed thus:
(define-syntax unwind-protect
(syntax-rules ()
((_ protected-form finalization-form)
(let ((g (make-guardian)))
(dynamic-wind
(lambda () #f)
(lambda ()
(let ((p (cons 'p '()))) ; p will only be alive as long as
; the continuation of
; unwind-protect is still alive
(g p)
(let ((result protected-form))
(reference-barrier p)
result))
(lambda ()
(guardian-try-collect g) ; try to collect p
(when (g)
finalization-form))))))))
This should safely clean up from the protected-form as soon as its continuation has been completely collected and will no longer be re-entered. Then the file-lines-generator example should be safe as well.
Daphne