[plt-scheme] Re: "unwind-protect" for PLT Scheme?
On Jul 4, Shriram Krishnamurthi wrote:
> You only need the before-thunk argument to do something if you have
> continuations somewhere deep within the body expression. (But note
> that even if you don't use continuations, you just might use a
> library function that does.) As long as neither you nor a library
> does, you can pass something trivial as before-thunk, such as void.
> That reduces to being the same as Lisp's unwind-protect.
Sorry to add another post on this, but I'll try to make a quick
summary of this whole thing in plain English (as much as I can call
what *I* write "plain English"):
* When no continuations are involved, `dynamic-wind' is perfectly fine
as is, and you can set up the resource either in the before thunk,
or before the whole call:
(let ([foo (open-output-file ...)])
(dynamic-wind void
(lambda () ...do stuff with foo...)
(lambda () ...close foo...)))
or
(let ([foo #f])
(dynamic-wind (lambda () (set! foo (open-output-file ...)))
(lambda () ...do stuff with foo...)
(lambda () ...close foo...)))
* When continuations are involved, then control can jump into or out
of the main thunk, and you have several options to choose from.
Here the before thunk becomes important since it can be called not
just once, but several times -- once when it starts, and a pair of
after-before calls for each jump out of and back into the main
thunk.
Just ignoring this makes it possible for your code to run into
problems that are similar in nature to writing code that is not
thread-safe: in both cases the problem is not in your own code, but
in the way it is being called or in the way library calls it
performs behave. And this analogy carries over to how people deal
with it: lots of user code just ignores the whole issue, but you
need to be more careful if you're writing some library, or parts of
the core language. [`call/cc' is notorious in exposing all kinds of
things about how things are implemented (for example, IIRC there was
a point in the (distant?) past where you could squeeze two different
behaviors out of mzscheme's `map' function because the
implementation was optimizing uses with a few functions).]
* Options for dealing with this are:
- Ignore the whole thing, and use the first form above.
- Arrange for the resource to be properly opened and closed on the
before/after thunks -- which means that you need to make sure
that, for example, you open the file in `append' mode so when
jumping out and back in the resource will be recreated. Of
course, this depends on the code doing something simple, like just
writing to the file.
- Forbid re-entry: you make a before thunk that remembers if it was
already called -- if that's the case, then you know that somehow
control went out and back in, and you make it throw an appropriate
error. This can be convenient if, for example, it is meaningless
to use the resource after it was already released.
- Intentionally avoid creating and releasing the resource in a
`dynamic-wind', so it will only be released when control leaves
normally. An example for this would be:
(let ([foo (open-output-file ...)])
(begin0 (...do stuff with foo...)
(...close foo...)))
or better:
(let ([foo (open-output-file ...)])
(define (close-foo) ...close foo...)
(with-handlers ([(lambda (x) #t)
(lambda (e) (close-foo) (raise e))])
(begin0 (...do stuff with foo...)
(close-foo))))
This works if when control happens to go out, it will also go back
in, so eventually control leaves as usual.
- There's a number of variants on these and subtle points involved
etc.
--
((lambda (x) (x x)) (lambda (x) (x x))) Eli Barzilay:
http://barzilay.org/ Maze is Life!