Feature request: guardian-try-collect Daphne Preston-Kendal (30 Nov 2023 23:33 UTC)
(missing)
Re: Feature request: guardian-try-collect Daphne Preston-Kendal (01 Dec 2023 07:56 UTC)
Re: Feature request: guardian-try-collect John Cowan (03 Dec 2023 05:16 UTC)
Re: Feature request: guardian-try-collect Daphne Preston-Kendal (03 Dec 2023 09:45 UTC)
Re: Feature request: guardian-try-collect John Cowan (03 Dec 2023 17:51 UTC)
Re: Feature request: guardian-try-collect Daphne Preston-Kendal (04 Dec 2023 12:19 UTC)
Re: Feature request: guardian-try-collect Marc Nieper-Wißkirchen (14 Dec 2023 15:06 UTC)

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