1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
Source: http://mumble.net/~campbell/blag.txt 2009-03-28 More simply addressing the confusion about UNWIND-PROTECT in Scheme Suppose you want to release a resource after a part of a program that uses the resource. For example, consider a connection to a database that should be disconnected, a file descriptor that should be closed, or a buffer for sensitive data such as a pass phrase that should be destroyed by zeros or random data. Once the resource is released, it can be neither reused nor readily reconstituted. Is the resource represented in Scheme by a single object? If so, ensure that with the object is registered a finalizer that releases the resource, so that even in the face of an interactive ^C at the REPL the resource will be released. All of the above examples qualify. (define (open-database-connection database-descriptor) (let ((handle (allocate-database-handle))) (register-finalizer handle %close-database-connection) ;; After this point, if we are interrupted -- say with ^C at ;; the REPL --, somewhere the handle will be recorded so that ;; its resources can be released even if the usual logic to ;; close the connection is never reached. (%open-database-connection database-descriptor handle) handle)) Is it /necessary/ for the resource to be released immediately after the part of the program that uses it? If so, use REWIND-PROTECT at <http://mumble.net/~campbell/scheme/rewind-protect.scm>. In this case, control can never re-enter the extent that uses the resource. Of the above examples, only a buffer for sensitive data qualifies. (define (call-with-pass-phrase prompt receiver) (let ((buffer (make-pass-phrase-buffer))) (rewind-protect (lambda () (prompt-for-pass-phrase prompt buffer) (receiver buffer)) (lambda () (clear-pass-phrase-buffer buffer))))) Is it /desirable/ for the resource to be released immediately after the part of the program that uses it? If so, use UNWIND-PROTECT at <http://mumble.net/~campbell/scheme/unwind-protect.scm>. Of the above examples, both the database connection and file descriptor qualify. (define (call-with-database-connection database-descriptor procedure) (let ((handle (open-database-connection database-descriptor))) (unwind-protect (lambda () (procedure handle)) (lambda () ;; If a continuation is captured inside PROCEDURE so that ;; this cannot execute immediately when PROCEDURE returns, ;; then closing the database connection here is redundant, ;; since registered with HANDLE is a finalizer that will ;; close the connection. However, provided that the ;; finalizer have no effect if the connection is already ;; closed, this redundancy is harmless and enables ;; UNWIND-PROTECT to close the connection immediately if ;; no such continuation has been captured. (close-database-connection handle))))) Rationale: Scheme's CWCC (or composable continuations) enables powerful control abstractions such as inverting control. For example, (FOR-EACH <procedure> <list>) applies <procedure> to each element of <list> in sequence. With CWCC, one can reify the iteration of the list by FOR-EACH into a first-class cursor object that can be advanced independently. This way, for example, we can traverse multiple lists in parallel using only the one-list FOR-EACH procedure. If we rather consider a procedure (FOR-EACH-QUERY-RESULT <procedure> <database-query>), the situation is not much different, except that FOR-EACH-QUERY-RESULT may entail the opening of a connection to a database, and the connection is a resource which should be released after the query results are finished being received. Using CWCC to invert control of FOR-EACH-QUERY-RESULT is just as valid as using CWCC to invert control of FOR-EACH, but if FOR-EACH-QUERY-RESULT uses REWIND-PROTECT (a common idiom in unfortunately much Scheme code), it inhibits control inversion. UNWIND-PROTECT allows the control inversion. Why involve finalization in UNWIND-PROTECT, rather than just associating a finalizer with the object representing the connection to the database? The difference is in the intent implied by the use of UNWIND-PROTECT that if control cannot re-enter the protected extent, the protector can be immediately applied when control exits the protected extent extent even if no garbage collection is about to run finalizers. Note that one cannot in general reconcile (1) needing to release the resource immediately after control exits the extent that uses it, and (2) enjoying the benefits of powerful control abstractions such as inversion of control. However, if (1) is relaxed to (1') needing to release the resource immediately after control /returns normally from/ the extent that uses it, then one can reconcile (1') and (2) by writing (CALL-WITH-VALUES (LAMBDA () <resource-usage>) (LAMBDA RESULTS <resource-release> (APPLY VALUES RESULTS))), or simply (BEGIN0 <resource-usage> <resource-release>) using the common name BEGIN0 for this idiom. (In Common Lisp, this is called PROG1.) Also note that condition systems are not involved; they are a red herring. Any condition handler that releases a resource should do so only when the condition implies that the resource is no longer usable; for example, if a database connection becomes desynchronized somehow and cannot proceed, then it is reasonable for the handler for this condition to close the connection and destroy any of its state. However, when handling conditions that do not imply that the resource is no longer usable, the resource should not be released. An enclosing handler may continue within a protected extent, or even exit and re-enter the protected extent, for instance while iterating over a reified database cursor procured by control inversion. Handling any condition by releasing the resource defeats these useful operations. At <http://mumble.net/~campbell/scheme/mit-unwind-protect.scm> is an implementation of UNWIND-PROTECT for MIT Scheme. This definition is lighter-weight than the portable one. However, it does not attempt to run the protector immediately if it can. Appendix A: John Cowan has asked for elaboration on the inversion of control for iteration with FOR-EACH. (define (list->stream list) (iteration-procedure->stream (lambda (receiver) (for-each receiver list)))) (define (iteration-procedure->stream iteration-procedure) (lazy (call-with-current-continuation (lambda (return-stream) (iteration-procedure (lambda (element) (call-with-current-continuation (lambda (return-to-iteration) (return-stream (stream-cons element (lazy (call-with-current-continuation (lambda (return-cdr) (set! return-stream return-cdr) (return-to-iteration)))))))))) (return-stream stream-nil))))) ;;; Simpler definition with reset & shift. (define (iteration-procedure->stream iteration-procedure) (lazy (reset (begin (iteration-procedure (lambda (element) (shift continue-iteration (stream-cons element (lazy (continue-iteration)))))) stream-nil)))) Appendix B, discussion question: Should UNWIND-PROTECT evaluate protection forms in the dynamic context of the call to UNWIND-PROTECT? This rules out using UNWIND-PROTECT within REWIND-PROTECT, which may be an onerous requirement.