Tobia Conforto scripsit: > I wonder how other compilers do it. For example, I find Java's (and > Python's) try/finally syntax quite useful. I've always thought > dynamic-wind was Scheme's equivalent construct, but it would appear I > was mistaken.
Here's the relevant writeup from Taylor Campbell's (Riastradh's) blag, slightly edited. DYNAMIC-WIND has long been a source of contention in the Lisp world. It serves a very particular purpose, but this purpose is not very well understood, and many think that this purpose is the same as the purpose of other Lisps' UNWIND-PROTECT. While the general idea of UNWIND-PROTECT is useful, it is not directly applicable in Scheme, because Scheme has more powerful control abstractions than other Lisps', and it therefore requires more powerful means of controlling them. (UNWIND-PROTECT form protection-form ...), a special form, evaluates the first form and returns its value, but before returning the value also executes the protection forms. Furthermore, if a throw in the primary form transfers control to outside the whole UNWIND-PROTECT form, this, too, will execute the protection forms. This allows programmers to *reliably* clean things up at certain points in the program -- for instance, to close open files, to shut down sockets, to release database handles, &c.; here is hypothetical example of its use: (define (call-with-input-file pathname procedure) (let ((input-port (open-input-file pathname))) (unwind-protect (procedure input-port) (close-input-port input-port)))) (DYNAMIC-WIND before during after), a procedure of three procedural parameters, calls the during thunk, ensuring that any transfer of control into it calls the before thunk, and that any transfer of control out of it calls the after thunk. Normal control transfers, i.e. the initial call to the during thunk and its final return, qualify as control transfers too. This means that, any time control is inside the dynamic extent of the during thunk, any state established by the before thunk will be in effect, but any time control is outside its dynamic extent, that state will be torn down by the after thunk. Here is an actual example of DYNAMIC-WIND's use, from the Edwin text editor: (define (with-current-local-bindings! thunk) (dynamic-wind (lambda () (install-buffer-local-bindings! (current-buffer))) thunk (lambda () (uninstall-buffer-local-bindings! (current-buffer))))) This ensures that, within THUNK, all Edwin variables have any values specified by the buffer's local bindings. This is the kind of use that DYNAMIC-WIND is appropriate for: a sort of control bracket that ensures the presence of certain dynamic state or context within a certain dynamic extent. Now, unfortunately, DYNAMIC-WIND can be seen as somewhat similar to UNWIND-PROTECT, because for basic cases (involving no sophisticated exploitation of Scheme's continuation reification) DYNAMIC-WIND with a null entrance thunk seems to be equivalent to UNWIND-PROTECT. This is misleading, however. Either control may return to the dynamic extent of the during thunk in the DYNAMIC-WIND, in which case the after thunk will be called twice -- which is unacceptably wrong --, or the before thunk really signals an error if it is called twice. This, though, violates the philosophy described in the first sentence of the R5RS: Programming languages should be designed not by piling feature on top of feature, but by removing the weaknesses and restrictions that make additional features appear necessary. Signalling an error in a DYNAMIC-WIND before thunk is usually a limitation and a weakness in a program. We have an *extremely* powerful means of abstraction of control -- CALL/CC[1] -- and we ought not to prevent it from working merely because we want to prevent a certain fragment of code from running more than once. This should, however, spur a deeper look at why the code is being run more than once in the first place. DYNAMIC-WIND is meant to establish local dynamic state for specific dynamic extents within programs, by setting it up in before thunks and tearing it down in after thunks. These operations, to set up and to tear down dynamic state, are meant to be reversible, local changes.[2] That is, permanent actions, such as closing a port, that one would put in a protection form of an UNWIND-PROTECT, are entirely inappropriate in a DYNAMIC-WIND after thunk. Yet the basic idea of UNWIND-PROTECT is still useful. What, exactly, though, is useful from UNWIND-PROTECT, and what is incompatible with Scheme? The concept of permanent finalization is useful, and the identification of a good point to perform it is useful. *Requiring* that it be performed at this point, though, is incompatible with Scheme, because this point may be passed several times, yet we must perform the finalization once and only once. Many languages, and many Scheme systems, provide ways to ensure that certain code be run when an object is no longer reachable. We can apply this same idea, in fact, to continuations! The protection thunk in an UNWIND-PROTECT of a lesser Lisp is run only once the continuation is about to become unreachable. The only difference between Scheme and CALL/CC-less Lisps is that the point at which continuations become unreachable is unfixed in Scheme. So we must use a general object finalization mechanism -- for the kind of object known as a continuation. This is easy to concoct, though: we need only to make an object, to associate with that object the protection thunk so that it will be executed when the object become unreachable, and to make a link from the continuation to that object. There is an implementation of this in MIT Scheme at the end of this entry. This, though, is non-optimal. The protection thunk may run at any time in the system. We should *like* it to be run as soon as control returns through the continuation in question, but this is valid only if that continuation has not been reified and will not ever be reused. We can say that a continuation is /dirty/ if it has been reified and may be instantiated again, and that a continuation is /clean/ if and only if there will be exactly one instance of it, which will be destroyed as soon as control returns through it. If we store a bit in any continuation involved with UNWIND-PROTECT, i.e. any continuation with an associated protection thunk, then we can initially clear the bit, to indicate that it is clean, and instrument CALL/CC to mark all such continuations as dirty by setting their bits. When control returns through a protected continuation, it checks the bit. If the bit is clear, then the continuation is clean, and it runs the protection thunk, because control will never again return to this point; it also deregisters any finalization procedure. If the bit is set, then the continuation is dirty, and general finalization system for objects of unlimited extent will handle the protection thunk. (We need to be careful in the case of a reified continuation instantiated on the stack whose heap storage has been reclaimed. The continuation will be marked dirty, but before control returns through it for the last time its protection thunk might be run, because the GC could finalize its heap storage.) Both DYNAMIC-WIND and UNWIND-PROTECT are useful for different applications in a language with powerful control abstractions such as Scheme. DYNAMIC-WIND, though, does not subsume the operation of UNWIND-PROTECT, and UNWIND-PROTECT cannot be taken literally from a language with weaker control abstractions. By examining the core of the intent of UNWIND-PROTECT, though, we can separate its actual purpose from an optimization. We can implement the actual purpose, and then analyze cases where the optimization can still be useful, in order to provide UNWIND-PROTECT efficiently in Scheme. The result is that we can retain the powerful control abstraction of CALL/CC and in the same language integrate the same idioms of resource release as other languages provide. Of course, there are still some limited cases where restriction is really necessary. There are a number of procedures in MIT Scheme that work with sensitive strings, and into which reentry is not an option, in order to enforce the proper destruction of sensitive information. In this case, DYNAMIC-WIND is appropriate to reject reentry and to nullify sensitive strings on exit. [1] Some would argue that composable continuations ought to be enough for everyone, but the issues here are exactly the same whether we use CALL/CC or composable continuations. [2] This is not *exactly* true, but symmetry is a very desirable property of DYNAMIC-WIND before & after thunks, and reversible set-up & tear-down operations are usual. -- And through this revolting graveyard of the universe the muffled, maddening beating of drums, and thin, monotonous whine of blasphemous flutes from inconceivable, unlighted chambers beyond Time; the detestable pounding and piping whereunto dance slowly, awkwardly, and absurdly the gigantic tenebrous ultimate gods --the blind, voiceless, mindless gargoyles whose soul is Nyarlathotep. (Lovecraft) John Cowan co...@ccil.org _______________________________________________ Chicken-users mailing list Chicken-users@nongnu.org http://lists.nongnu.org/mailman/listinfo/chicken-users