unwind-protect(2015 February 13)
NOTE 2015 February 19 bis for part 2.
unwind-protect has the purpose of releasing resources that must be
allocated synchronously with respect to a chunk of code; its syntax is:
(unwind-protect ?body ?clean-up0 ?clean-up ...)
and it can be used as:
#!vicare (let ((port (open-file-output-port "file.ext"))) (unwind-protect (put-bytevector port '#ve(ascii "ciao")) (close-output-port port)))
first we allocate a resource (in this case the port); then we use it in the
?body form; finally we release it in the ?clean-up forms. The
?clean-up forms are executed whether ?body performs a normal return or
raises an exception. When ?body returns: the return value of
unwind-protect is the return value of ?body.
It is clear that
unwind-protect is a very useful operator: in a way or the
other, a language must provide a way to release synchronous resources. We
might be tempted to rely on the garbage collector finalisers (under Vicare:
There have been
discussions about how to implement
unwind-protect in Scheme and some
proposals; none of them
provided a definitive solution (where definitive means: problem solved and
syntax included in the standard language). At
master branch implements an unwind–protection mechanism
that, at its core, is available through the built–in macro
with-unwind-protection exported by the library
(vicare); the syntax
(with-unwind-protection ?clean-up ?thunk)
where ?thunk performs the job and the procedure ?clean-up releases resources.
The above example would be implemented as:
#!vicare (let ((port (open-file-output-port "file.ext"))) (with-unwind-protection (lambda (why) (close-output-port port)) (lambda () (put-bytevector port '#ve(ascii "ciao")))))
The built–in macro
unwind-protect is still available as simple wrapper
(unwind-protect ?body ?cleanup0 ?clean-up ...) → (with-unwind-protection (lambda (dummy) ?cleanup0 ?clean-up ...) (lambda () ?body))
the built–in macros
with-compensations are also
implemented on top of
So, how does it work? The idea is to define the concept of dynamic extent termination of a call to function; this concept is defined by Vicare’s unwind–protection mechanism and it is not a R6RS concept. To understand the unwind–protection mechanism we must understand the concepts “dynamic extent of a function call” and “dynamic environment”.
The procedure ?clean-up is called when the dynamic extent of the invocation of
?thunk terminates; dynamic extent termination is different from
dynamic extent exiting as determined by
dynamic-wind. When the
execution flow exits the dynamic extent of a function call: such extent might also
terminate, but not all the exits are also terminations.
In this discussion, we consider the syntax use:
(with-unwind-protection ?clean-up ?thunk)
the dynamic extent of a call to ?thunk is terminated, and so ?clean-up is invoked, when:
guard, but only when a clause of
guardhas a test expression returning non–false. If we do:
(guard (E (?test ?expr)) (with-unwind-protection ?clean-up (lambda () (error #f "I know what you did"))))
this is what happens:
guardand it returns non–false.
with-unwind-protectionand its return values discarded. ?clean-up is applied to the symbol ‘exception’.
guardand its return values are returned to the continuation of
return, as defined by the library
(vicare), to escape from a form that encloses an unwind–protection syntax. ?clean-up is applied to the symbol ‘escape’.
As bound by the loop syntaxes
until, … and the syntax
returnable: these fluid syntaxes reinstate a continuation at the beginning
or outside of
until, … and
perform special operations to terminate the dynamic extent of the call to
?thunk in an unwind–protection form.
The dynamic extent of a call to ?thunk is not terminated, and so ?clean-up is not invoked, when:
raise-continuable, and such call performs a normal return to ?thunk.
yieldis called from within ?thunk to hand control to another coroutine.
About the termination of the dynamic extent of ?thunk, we must acknowledge that:
guardintercepts it: if a test expression in a clause of
guardraises a second exception, the ?clean-up may not be called.
dynamic-windraise a second exception: the clean–up operation may fail (and, for example, leave the resource unreleased and in an incorrect state).
Things to notice:
raiseany object: it is better to always raise a condition object (possibly compound), so that the test expressions in
guarduses can just be condition object type predicates; such predicates never raise exceptions.
The problem of the second exception does not haunt only Vicare; if I understand
defined by Common Lisp also presents cases where the clean–up forms may not be
The unwind–protection mechanism has the purpose of evaluating code when the dynamic
extent of a function call terminates; it can be used to release synchronous
resources: resources whose life must terminate when the dynamic extent of a function
call terminates. Whenever possible, we must take care of handling exceptions with
guard before they cross the unwind–protection boundary. So, with
compensations, we might do:
(with-compensations (guard (E (?test ?expr) ...) (define ?resource-reference (compensate ?alloc (with ?release))) ... ?body0 ?body ...))
or, if we like
(with-compensations (try (letrec ((?resource-reference (compensate ?alloc (with ?release))) ...) ?body0 ?body ...) (catch E ((?condition-type) ?expr) ...)))