Unwind-protect in portable Scheme
In a web article [4], Kent Pitman, one of the authors of
the Scheme standard [3], observes that Scheme's
non-local control operator call/cc is misdesigned
because it thwarts the creation of a pragmatic
unwind-protect facility that would have worked in the
context of Scheme. To solve this problem, he proposes
that one of two modified versions of call/cc should have
been provided in the Scheme standard, which would
then permit a suitable unwind-protect, while still
providing full continuations.
This text will show that we can indeed implement these
modified call/ccs and their companion
unwind-protect in standard Scheme. The
approach follows that specified in ``Constraining
Control'', a 1985 paper by Daniel Friedman and
Christopher Haynes [2]. In it they note that raw
call/cc is too powerful to use unaltered in all
programming scenarios, but that it can be constrained quite easily. In essence, they define new
call/cc operators that call the original
call/cc, but instead of directly calling the
call/cc-argument on the continuation, they call it
on a continuation object or cob, which is a
procedure that performs whatever additional
constraining tasks are required before calling the
actual continuation.1
Indeed, Friedman and Haynes already tackle the problem of
specifying an unwind-protect for Scheme, and
observe that because Scheme's continuations can be
called after the context that defined them has exited,
there is a choice of meaningful
unwind-protect semantics. They recognize at least
four semantics, and proceed to implement one. The two
proposed by Pitman aren't in their four, but can be
obtained using the same cob tactics that they
illustrate.
Let us clearly identify what is given and what the goal
is. We have standard Scheme, which has call/cc and
dynamic-wind.2 We assume a fluid-let
form
that works correctly in the presence of call/cc.
While fluid-let isn't standard, it can be defined
as a macro.3
For each of the two proposals, we will define a new
call/cc and a corresponding
unwind-protect
that works correctly with it.4
Section 1 defines an unwind-protect
that recognizes escaping continuations;
Section 2 defines one that recognizes
last-use continuations.
1 Once these new operators are
defined, the original call/cc would have to be
retired so as not to interfere with the functioning of
the new operators. This can be done using Scheme's
lexical scoping and assignment. A good argument for
why this retirement should be done explicitly and not
inherently in the standard is that the present
modification is but one of many possible, and we should
leave the door open for a different modification.
2 Only unary continuations are considered. I will ignore the presence of Scheme's multiple values, as they tend to add bulk, not illumination, to the code.
3 (fluid-let ((x v) ...) e ...)
expands to
(let ((other-x v) ...) (let ((swap (lambda () (let ((temp other-x)) (set! other-x x) (set! x temp)) ...))) (dynamic-wind swap (lambda () e ...) swap)))
Expression of this macro with Scheme's
syntax-rules or syntax-case is a bit long-winded though quite
possible, and is left as an exercise.
4 For convenience, we will define
an unwind-protect-proc procedure, and then define
the unwind-protect form in terms of the procedure.