Google 网上论坛不再支持新的 Usenet 帖子或订阅项。历史内容仍可供查看。

A new specification for dynamic-wind, call/wc, and call/nwc.

已查看 2 次
跳至第一个未读帖子

Al Petrofsky

未读,
2002年7月11日 07:21:572002/7/11
收件人
Shortly after r5rs was released, Matthias Blume proposed having two
kinds of call/cc, which I will call call/wc and call/nwc, or
call-with-winding-continuation and call-with-non-winding-continuation.
Invoking an escape procedure created by call/nwc never causes a
dynamic-wind's before or after thunk to be invoked.

The exact desired interaction of call/nwc with dynamic-wind is a bit
tricky. If a program does not use call/nwc, then call/wc's semantics
are equivalent to those of call/cc in r5rs. If a program does not use
dynamic-wind, then call/wc and call/nwc are both equivalent to
call/cc. That much is clear.

Things get trickier if a program uses both call/nwc and dynamic-wind.
Let us classify the entries and exits of a dynamic-wind's middle thunk
as winding and nonwinding. The initial entry is a winding entry. An
exit by return of the middle thunk is a winding exit. Any entry or
exit effected by a call/wc escape procedure is a winding entry or
exit. Any entry or exit effected by a call/nwc escape procedure is a
nonwinding entry or exit.

According to Blume's specification, all winding entries and exits
invoke the before or after thunk, and all nonwinding ones do not. The
problem with this is that it violates the expectation that the before
and after thunks are only executed in alternate before-after order:

(let* ((l '())
(k (dynamic-wind (lambda () (set! l (cons 'enter l)))
(lambda () (call/nwc (lambda (k) k)))
(lambda () (set! l (cons 'exit l))))))
(if k (k #f) (reverse l)))
=> (enter exit exit)

(let* ((l '())
(k (call/nwc (lambda (k)
(dynamic-wind (lambda () (set! l (cons 'enter l)))
(lambda () (call/wc k))
(lambda () (set! l (cons 'exit l))))))))
(if k (k #f) (reverse l)))
=> (enter enter exit)

In 1998, Will Clinger wrote:

> They can co-exist in some sense, but I don't think they can co-exist
> very well.
>
> The main reason for having DYNAMIC-WIND in the language is to provide
> a guarantee that
>
> (dynamic-wind thunk1 thunk2 thunk3)
>
> can't return a value unless both thunk1 and thunk3 have been
> evaluated, in that order. (They might be evaluated several times, in
> fact, but always in that order, and these evaluations are never
> nested.)
>
> A use of R4RS CALL-WITH-CURRENT-CONTINUATION would destroy this
> guarantee, which is part of the R5RS specification of DYNAMIC-WIND.

We can preserve this guarantee by using the following rules:

Nonwinding entries and exits never invoke before and after thunks.
A winding entry only invokes the before thunk if the before and
after thunks have been invoked an equal number of times. A winding
exit only invokes the after thunk if the after thunk has been
invoked one fewer times than the before thunk.

Here is an alternative way to state that:

A before or after thunk is only invoked when a winding entry or
exit is made and the current state of the thunks is "proper". The
state is initially proper and is reset to proper after every
winding entry or exit. It is toggled between proper and improper
by any nonwinding entry or exit.

Here is an implementation (not thoroughly tested):

;; Invoking a node reroots the tree to that node, invoking before and
;; after thunks as appropriate, and then overwrites the nodes' fields
;; with new values.
;;
;; The root node is always the only node in which parent is #f.
;;
;; (Note that the before and after thunks are swapped as nodes are
;; traversed. That's what makes the whole thing work.)
(define (make-node before after parent)
(define proper? #t)
(define (node wind? new-before new-after new-parent new-proper?)
(if parent
(begin (parent wind? after before node (or wind? (not proper?)))
(set! root node)
(set! parent #f)
(and wind? proper? (before))))
(set! before new-before)
(set! after new-after)
(set! parent new-parent)
(set! proper? new-proper?))
node)

(define root (make-node #f #f #f))

(define (reroot! wind? node)
(node wind? #f #f #f #f))

(define (wrap-k wind? raw-k)
(let ((saved root))
(lambda vals
(reroot! wind? saved)
(apply raw-k vals))))

(define base-call/cc call-with-current-continuation)

(define (call/wc proc) (base-call/cc (lambda (k) (proc (wrap-k #t k)))))
(define (call/nwc proc) (base-call/cc (lambda (k) (proc (wrap-k #f k)))))

(define (dynamic-wind before during after)
(call/wc (lambda (wc)
(reroot! #t (make-node before after root))
(call-with-values during wc))))

;; Uncomment one of these according to taste:
;;(define call-with-current-continuation call/wc)
;;(define call-with-current-continuation call/nwc)

(let* ((l '())
(k (dynamic-wind (lambda () (set! l (cons 'enter l)))
(lambda () (call/nwc (lambda (k) k)))
(lambda () (set! l (cons 'exit l))))))
(if k (k #f) (reverse l)))
=> (enter exit)

(let* ((l '())
(k (call/nwc (lambda (k)
(dynamic-wind (lambda () (set! l (cons 'enter l)))
(lambda () (call/wc k))
(lambda () (set! l (cons 'exit l))))))))
(if k (k #f) (reverse l)))
=> (enter exit)

(let* ((l '())
(k (dynamic-wind (lambda () (set! l (cons 'enter l)))
(lambda () (call/wc (lambda (k) k)))
(lambda () (set! l (cons 'exit l))))))
(if k (k #f) (reverse l)))
=>

-al

Al Petrofsky

未读,
2002年7月11日 07:39:162002/7/11
收件人
[I apologize for the double post. I accidentally hit send before I
was finished. I tried to cancel the first version, but that doesn't
seem to have worked.]

Shortly after r5rs was released, Matthias Blume proposed having two
kinds of call/cc, which I will call call/wc and call/nwc, or

call-with-winding-continuation and call-with-nonwinding-continuation.

(define base-call/cc call-with-current-continuation)

;; Tests:

(let* ((l '())
(k (dynamic-wind (lambda () (set! l (cons 'enter l)))
(lambda () (call/nwc (lambda (k) k)))
(lambda () (set! l (cons 'exit l))))))
(if k (k #f) (reverse l)))
=> (enter exit)

(let* ((l '())
(k (call/nwc (lambda (k)
(dynamic-wind (lambda () (set! l (cons 'enter l)))
(lambda () (call/wc k))
(lambda () (set! l (cons 'exit l))))))))
(if k (k #f) (reverse l)))
=> (enter exit)

(let* ((l '())
(k (dynamic-wind (lambda () (set! l (cons 'enter l)))
(lambda () (call/wc (lambda (k) k)))
(lambda () (set! l (cons 'exit l))))))
(if k (k #f) (reverse l)))

=> (enter exit enter exit)

-al

Joe Marshall

未读,
2002年7月11日 13:19:302002/7/11
收件人
Here is some test code for dynamic-wind.

;;; The purpose of dynamic-wind is to provide an analogy to the
;;; UNWIND-PROTECT form provided by Common Lisp. This functionality
;;; is similarly provided by the try ... finally form found in C++ and
;;; Java. The semantics of these forms do not translate well to
;;; Scheme where because of first-class continuations it may be
;;; possible to `return' from a form multiple times.
;;;
;;; Informally, the primary function is to ensure that code in the
;;; `cleanup' clause is executed when the code in the `protected'
;;; clause exits whether this exit occurs because the form returned a
;;; value normally or whether an exceptional transfer of control
;;; occurred. I'd like to formalize this.

;;; I'll define a history which is a mechanism for recording
;;; `events' of interest. Creating an event notes it in
;;; the history, once an event is created, it may be tested
;;; for occurrance and whether it was noted prior to another
;;; event.

(define (make-history receiver)
(let ((the-history '())
(make-event (lambda (name) (list 'event name))))

(let ((note-event! (lambda (name)
(let ((event (make-event name)))
(set! the-history (cons event the-history))
event)))

(occurred? (lambda (event)
(if (member event the-history) #t #f)))

(prior? (lambda (event1 event2)
(let ((occurrance2 (member event2 the-history)))
(if occurrance2
(let ((occurrance1 (member event1 occurrance2)))
(if occurrance1 #t #f))
#f)))))

(receiver note-event! occurred? prior?))))

;;; There are three events of interest in an UNWIND-PROTECT:
;;; Executing the body, executing the cleanup, returning control
;;; to the caller. Event-1 is recorded when we execute the body.
;;; Event-3 is recorded upon returning from the body.
;;;
;;; If both Event-1 and Event-3 are recorded, then we `returned'
;;; from the body in some manner. Therefore, event-2 must be
;;; recorded, and it must be subsequent to event-1 and prior to
;;; event-3.

(define (assert condition)
(if (not condition) (error "Assertion failed.")))

(define (test-program client-wrapper)
(make-history
(lambda (note-event! occurred? prior?)
(let ((event-1 #f)
(event-2 #f)
(event-3 #f))

(client-wrapper
(lambda (client-inner)
(dynamic-wind
(lambda () ())
(lambda () (set! event-1 (note-event! 'body)) (client-inner))
(lambda () (set! event-2 (note-event! 'cleanup))))))

(set! event-3 (note-event! 'after))

(if (and (occurred? event-1) ;; we executed the body
(occurred? event-3)) ;; we returned somehow
(begin
(assert (occurred? event-2))
(assert (prior? event-1 event-2))
(assert (prior? event-2 event-3))
'copacetic)

;; if we don't execute the body, we don't need to clean up.
'trivially-copacetic)))))

;;; Here are some sample client programs.

;;; LAZY-CLIENT doesn't execute any body code, it is trivially
;;; copecetic.

;;; RETURNING-CLIENT returns the value #f from the body, it should be
;;; copacetic.

;;; THROWING-CLIENT throws a #f out of the protected form, it should
;;; also be copacetic.

(define (lazy-client protected)
#f)

(define (returning-client protected)
(protected (lambda () #f)))

(define (throwing-client protected)
(call-with-current-continuation
(lambda (k)
(protected (lambda () (k #f))))))

;;; For the sake of referential transparency, the test-program cannot
;;; know whether the client program will throw or not.
;;;
;;; Similarly, the client program cannot know whether the test program
;;; does a dynamic-wind.

;;; If dynamic-wind `works', then it is impossible to write a function
;;; such that (test-program function) returns a value other than 'copacetic
;;; or 'trivially-copacetic.

Al Petrofsky

未读,
2002年7月11日 15:11:202002/7/11
收件人
"Joe Marshall" <prunes...@attbi.com> writes:

> Here is some test code for dynamic-wind.

> (define (throwing-client protected)


> (call-with-current-continuation
> (lambda (k)
> (protected (lambda () (k #f))))))
>
> ;;; For the sake of referential transparency, the test-program cannot
> ;;; know whether the client program will throw or not.
> ;;;
> ;;; Similarly, the client program cannot know whether the test program
> ;;; does a dynamic-wind.

Right, that's why a client program would use call/wc (r5rs call/cc)
if, as in throwing-client, it is going to jump across code in the test
program and then return control to the test program before jumping
back.

If, however, the client program just wants to jump around in its own
code before returning normally to the test program, then the
appropriate construct is call/nwc:

(define (jumping-client protected)
((call/nwc (lambda (k) (lambda (x) (protected (lambda () (call/nwc k))))))
'hello-from-out-here))

-al

Joe Marshall

未读,
2002年7月11日 16:45:342002/7/11
收件人

"Al Petrofsky" <a...@petrofsky.org> wrote in message news:877kk23...@radish.petrofsky.org...

Right, but here's the rub:

I claim that the only way to correctly use a `non-winding' continuation
is in a form such as the above, that other uses of a `non-winding' continuation
would cause my test-program to fail, and that since the control-flow above
is essentially straight-line, there is no need for it. (i.e., it is no
different from (protected (lambda () 'hello-from-out-here))

Al Petrofsky

未读,
2002年7月11日 18:12:562002/7/11
收件人
"Joe Marshall" <prunes...@attbi.com> writes:
> "Al Petrofsky" <a...@petrofsky.org> wrote:

> > Right, that's why a client program would use call/wc (r5rs call/cc)
> > if, as in throwing-client, it is going to jump across code in the test
> > program and then return control to the test program before jumping
> > back.
> >
> > If, however, the client program just wants to jump around in its own
> > code before returning normally to the test program, then the
> > appropriate construct is call/nwc:
> >
> > (define (jumping-client protected)
> > ((call/nwc (lambda (k) (lambda (x) (protected (lambda () (call/nwc k))))))
> > 'hello-from-out-here))
>
> Right, but here's the rub:
>
> I claim that the only way to correctly use a `non-winding'
> continuation is in a form such as the above, that other uses of a
> `non-winding' continuation would cause my test-program to fail, and
> that since the control-flow above is essentially straight-line,
> there is no need for it. (i.e., it is no different from (protected
> (lambda () 'hello-from-out-here))

I'm not sure what you mean by "essentially straight-line". The
continuation captured as k is used twice (once by a normal return and
once by using the escape procedure). A more complicated example would
be a client that jumps back and forth between client code called by
test-program-1 and client code called by test-program-2. Wouldn't
that also be a correct use of nonwinding continuations? Supporting
such non-linear control flow is the whole point of having call/cc
rather than just call/ec (i.e., outwards-only escape procedures). The
fact that none of your code uses the before thunk indicates that what
you really want is just call/ec and unwind-protect. If so, then why
not call them that, rather than call/cc and dynamic-wind? The idea
that you can simultaneously have full call/cc and something that does
resource cleanup like unwind-protect is a fallacy.

-al

Joe Marshall

未读,
2002年7月12日 01:42:412002/7/12
收件人

"Al Petrofsky" <a...@petrofsky.org> wrote in message news:87wus13...@radish.petrofsky.org...

> "Joe Marshall" <prunes...@attbi.com> writes:
> > "Al Petrofsky" <a...@petrofsky.org> wrote:
>
> > > Right, that's why a client program would use call/wc (r5rs call/cc)
> > > if, as in throwing-client, it is going to jump across code in the test
> > > program and then return control to the test program before jumping
> > > back.
> > >
> > > If, however, the client program just wants to jump around in its own
> > > code before returning normally to the test program, then the
> > > appropriate construct is call/nwc:
> > >
> > > (define (jumping-client protected)
> > > ((call/nwc (lambda (k) (lambda (x) (protected (lambda () (call/nwc k))))))
> > > 'hello-from-out-here))
> >
> > Right, but here's the rub:
> >
> > I claim that the only way to correctly use a `non-winding'
> > continuation is in a form such as the above, that other uses of a
> > `non-winding' continuation would cause my test-program to fail, and
> > that since the control-flow above is essentially straight-line,
> > there is no need for it. (i.e., it is no different from (protected
> > (lambda () 'hello-from-out-here))
>
> I'm not sure what you mean by "essentially straight-line". The
> continuation captured as k is used twice (once by a normal return and
> once by using the escape procedure).

Whoops! You are right, but....

> A more complicated example would
> be a client that jumps back and forth between client code called by
> test-program-1 and client code called by test-program-2. Wouldn't
> that also be a correct use of nonwinding continuations?

The test-program is perhaps too simple: it only establishes one
dynamic context and ensures that when that context is exited that
the after thunk runs. A more complete test could be written that
also shows that the before thunk runs on entry, that the before thunk
runs exactly the same number of times as the after thunk after exiting,
and that the dynamic contexts form a directed acyclic graph, etc.


> The
> fact that none of your code uses the before thunk indicates that what
> you really want is just call/ec and unwind-protect. If so, then why
> not call them that, rather than call/cc and dynamic-wind?

The fact that none of my code uses the before thunk indicates that
I was attempting to make the test code as simple as possible. The
code also does not test for multiple exits (it just checks that there
is at least one call to the exit thunk between `inside' and `outside').

> The idea
> that you can simultaneously have full call/cc and something that does
> resource cleanup like unwind-protect is a fallacy.

I'm not sure I understand what you are saying with this statement,
that dynamic-wind won't work in the presence of non-winding continuations
(I agree), or that dynamic-wind doesn't provide a cleanup/messyup
mechanism (I disagree).

Certainly you cannot have a usable dynamic-wind if call-with-current-continuation
doesn't obey it, and if there is a mechanism for `re-establishing'
a dynamic context then it can go in the before thunk.
If the exit thunk performs some irreversible action, you obviously
can't roll-back time. But even BEGIN fails in this way:
(begin
(call-with-current-continuation (k) (set! again k) ....)
(blow-something-up)
(k #f))

This will `not work'.

But dynamic-wind provides a solution!

(let ((irreversible #f))
(dynamic-wind (lambda () (if irreversible (error "Too bad, you lose."))
(lambda () ....)
(lambda () (set! irreversible #t))))

And now we have a one-shot boundary.


Al Petrofsky

未读,
2002年7月12日 05:25:172002/7/12
收件人
"Joe Marshall" <prunes...@attbi.com> writes:
> "Al Petrofsky" <a...@petrofsky.org> wrote:
> > "Joe Marshall" <prunes...@attbi.com> writes:

> > > I claim that the only way to correctly use a `non-winding'
> > > continuation is in a form such as the above, that other uses of a
> > > `non-winding' continuation would cause my test-program to fail, and
> > > that since the control-flow above is essentially straight-line,
> > > there is no need for it. (i.e., it is no different from (protected
> > > (lambda () 'hello-from-out-here))
> >
> > I'm not sure what you mean by "essentially straight-line". The
> > continuation captured as k is used twice (once by a normal return and
> > once by using the escape procedure).
>
> Whoops! You are right, but....
>
> > A more complicated example would be a client that jumps back and
> > forth between client code called by test-program-1 and client code
> > called by test-program-2. Wouldn't that also be a correct use of
> > nonwinding continuations?
>
> The test-program is perhaps too simple: it only establishes one
> dynamic context and ensures that when that context is exited that
> the after thunk runs. A more complete test could be written that
> also shows that the before thunk runs on entry, that the before thunk
> runs exactly the same number of times as the after thunk after exiting,
> and that the dynamic contexts form a directed acyclic graph, etc.

None of that would be violated by a client that jumps around among its
own continuations using call/nwc and then returns normally from the
most recent call made by one of the test-programs.

> > The idea that you can simultaneously have full call/cc and
> > something that does resource cleanup like unwind-protect is a
> > fallacy.
>
> I'm not sure I understand what you are saying with this statement,
> that dynamic-wind won't work in the presence of non-winding continuations
> (I agree), or that dynamic-wind doesn't provide a cleanup/messyup
> mechanism (I disagree).

It provides a cleanup/messyup mechanism, which is very different from
a true cleanup mechanism, and is only suitable for things that can be
messied up. A true cleanup mechanism, like lisp's unwind-protect,
only runs the cleanup when there is no way to return. In a scheme
with full call/cc, that can only be done by something like Olin
Shivers's when-finished, which would be tied into the garbage
collection of escape procedures.

> If the exit thunk performs some irreversible action, you obviously
> can't roll-back time. But even BEGIN fails in this way:

> (begin
> (call-with-current-continuation (k) (set! again k) ....)
> (blow-something-up)
> (k #f))
>
> This will `not work'.
>
> But dynamic-wind provides a solution!
>
> (let ((irreversible #f))
> (dynamic-wind (lambda () (if irreversible (error "Too bad, you lose."))
> (lambda () ....)
> (lambda () (set! irreversible #t))))

In the begin example, if "...." jumps out and back, nothing is blown
up. In the dynamic-wind example, such attempts to use call/cc in a
non-call/ec manner lose.

In other words, your argument that dynamic-wind only "works" if there
is no call/nwc is equivalent to the argument that call/cc only "works"
if it can be used in a non-call/ec manner without things arbitrarily
breaking.

As I see it, these are the options:

1. Have call/cc only, as in r4rs.
2. Have call/ec and unwind-protect only, as in (essentially) lisp.
3. Have a "non-working" call/cc, named call/cc, and dynamic-wind, as in r5rs.
4. Have call/wc, call/nwc, and a "non-working" dynamic-wind.

I find options 1 and 2 both quite reasonable. What's so annoying
about option 3 is that it uses the name call/cc. In option 4, neither
of the names "call/cc" and "unwind-protect" are used, to reflect the
fact that they are mutually exclusive.

I should have pointed out in the first post of this thread that this
new specification for dynamic-wind does not resolve all the conflict
inherent in having something like call/cc and something like
unwind-protect. My point is just that there is a compromise that is
superior (if not by much) to a previously discussed compromise. It is
superior to Blume's old call/nwc proposal in that it gives call/nwc
just as much power while preserving more (but not all) of the
properties that dynamic-wind has in a language without call/nwc.

-al

Joe Marshall

未读,
2002年7月12日 18:16:072002/7/12
收件人

"Al Petrofsky" <a...@petrofsky.org> wrote in message news:87ptxt2...@radish.petrofsky.org...

> "Joe Marshall" <prunes...@attbi.com> writes:
> >
> > The test-program is perhaps too simple: it only establishes one
> > dynamic context and ensures that when that context is exited that
> > the after thunk runs. A more complete test could be written that
> > also shows that the before thunk runs on entry, that the before thunk
> > runs exactly the same number of times as the after thunk after exiting,
> > and that the dynamic contexts form a directed acyclic graph, etc.
>
> None of that would be violated by a client that jumps around among its
> own continuations using call/nwc and then returns normally from the
> most recent call made by one of the test-programs.

Yes, you could write a client that did any arbitrary thing so long
as the test program cannot detect it. However, such a client would
*not* be able to call *any* `library' functions because the client
has no control over whether other code might capture or invoke a
continuation in a manner visible to the test code. There isn't an
awful lot you can do without calling `library' code in Scheme.

> > > The idea that you can simultaneously have full call/cc and
> > > something that does resource cleanup like unwind-protect is a
> > > fallacy.
> >
> > I'm not sure I understand what you are saying with this statement,
> > that dynamic-wind won't work in the presence of non-winding continuations
> > (I agree), or that dynamic-wind doesn't provide a cleanup/messyup
> > mechanism (I disagree).
>
> It provides a cleanup/messyup mechanism, which is very different from
> a true cleanup mechanism, and is only suitable for things that can be
> messied up. A true cleanup mechanism, like lisp's unwind-protect,
> only runs the cleanup when there is no way to return.

I disagree that these are `very different'. They both establish a
dynamic context and unestablish the context on exit. The difference
is that the context may be re-entered in Scheme, and it cannot in
Lisp. This means that you will lose if you attempt to re-enter
a dynamic context that cannot be re-created.

But so what? If I exit a context that cannot be re-created, life
is hard. It is the same as if I print something to the screen that I
wish to revoke later. But just because dynamic-wind doesn't solve
the insolvable doesn't make it useless for the cases where you
*can* re-create the dynamic context.

> > If the exit thunk performs some irreversible action, you obviously
> > can't roll-back time. But even BEGIN fails in this way:
>
> > (begin
> > (call-with-current-continuation (k) (set! again k) ....)
> > (blow-something-up)
> > (k #f))
> >
> > This will `not work'.
> >
> > But dynamic-wind provides a solution!
> >
> > (let ((irreversible #f))
> > (dynamic-wind (lambda () (if irreversible (error "Too bad, you lose."))
> > (lambda () ....)
> > (lambda () (set! irreversible #t))))
>
> In the begin example, if "...." jumps out and back, nothing is blown
> up. In the dynamic-wind example, such attempts to use call/cc in a
> non-call/ec manner lose.
>
> In other words, your argument that dynamic-wind only "works" if there
> is no call/nwc is equivalent to the argument that call/cc only "works"
> if it can be used in a non-call/ec manner without things arbitrarily
> breaking.

Not at all. I don't expect call-with-current-continuation to be able
to roll back time. But I *do* expect to be able to use it re-entrantly
provided that the enter and exit thunks *are* reversible.

> As I see it, these are the options:
>
> 1. Have call/cc only, as in r4rs.
> 2. Have call/ec and unwind-protect only, as in (essentially) lisp.
> 3. Have a "non-working" call/cc, named call/cc, and dynamic-wind, as in r5rs.
> 4. Have call/wc, call/nwc, and a "non-working" dynamic-wind.
>
> I find options 1 and 2 both quite reasonable. What's so annoying
> about option 3 is that it uses the name call/cc. In option 4, neither
> of the names "call/cc" and "unwind-protect" are used, to reflect the
> fact that they are mutually exclusive.

Options 1 and 4 are equivalent. The point of unwind-protect
or dynamic-wind is to provide a *guarantee*. If you can break the
guarantee, then it isn't a guarantee anymore.

> I should have pointed out in the first post of this thread that this
> new specification for dynamic-wind does not resolve all the conflict
> inherent in having something like call/cc and something like
> unwind-protect. My point is just that there is a compromise that is
> superior (if not by much) to a previously discussed compromise. It is
> superior to Blume's old call/nwc proposal in that it gives call/nwc
> just as much power while preserving more (but not all) of the
> properties that dynamic-wind has in a language without call/nwc.

That's fine, but it still is missing the *essential* property of
dynamic-wind: that it makes a guarantee.

David Rush

未读,
2002年7月12日 18:50:062002/7/12
收件人
"Joe Marshall" <prunes...@attbi.com> writes:
> "Al Petrofsky" <a...@petrofsky.org> wrote in message news:87wus13...@radish.petrofsky.org...

> > The idea
> > that you can simultaneously have full call/cc and something that does
> > resource cleanup like unwind-protect is a fallacy.
>
> I'm not sure I understand what you are saying with this statement,
> that dynamic-wind won't work in the presence of non-winding continuations
> (I agree),

Errr, not to butt in or anything, but

"Joe Marshall" wrote in <mrXW8.304476$R61.2...@rwcrnsc52.ops.asp.att.net>:
> "Ronald Schröder" wrote in message <c23fb729.02070...@posting.google.com>
> > The FAQ at www.schemers.org also states that a wind-safe call/cc may
> > be implemented using the wind-unsafe call/cc.
>
> And vice versa.

To me this implied that you thought it *was* possible to have both
call/wc and call/nwc in the same language, particularly in the case
when call/wc is the primitive operation. Have you changed your
opinion? Or have I misunderstood you?

I also asked how you would do such a thing w/out undoing R5RS call/cc
+ dynamic-wind in the first place. The only outline implementation
presented (not by you, Joe) essentially ignored that constraint. Do
you have a suggestion?

david rush
--
Scheme: When the line noise gets too much for you.
-- Anton van Straaten (the Scheme Marketing Dept from c.l.s)

Al Petrofsky

未读,
2002年7月13日 14:52:162002/7/13
收件人
"Joe Marshall" <prunes...@attbi.com> writes:
> "Al Petrofsky" <a...@petrofsky.org> wrote:

> > [dynamic-wind] provides a cleanup/messyup mechanism, which is very


> > different from a true cleanup mechanism, and is only suitable for
> > things that can be messied up. A true cleanup mechanism, like
> > lisp's unwind-protect, only runs the cleanup when there is no way
> > to return.

> just because dynamic-wind doesn't solve the insolvable doesn't make


> it useless for the cases where you *can* re-create the dynamic
> context.

I agreed that dynamic-wind is suitable for the cases where the after
thunk is reversible.

> > > (begin
> > > (call-with-current-continuation (k) (set! again k) ....)
> > > (blow-something-up)
> > > (k #f))

> > > (let ((irreversible #f))


> > > (dynamic-wind (lambda () (if irreversible (error "Too bad, you lose."))
> > > (lambda () ....)
> > > (lambda () (set! irreversible #t))))
> >
> > In the begin example, if "...." jumps out and back, nothing is blown
> > up. In the dynamic-wind example, such attempts to use call/cc in a
> > non-call/ec manner lose.

> I don't expect call-with-current-continuation to be able to roll
> back time.

Neither do I. The problem is when you have an irreversible after
thunk and it gets executed just because you used call/cc to
temporarily escape and return. The program breaks even though it
should be perfectly possible for it to run correctly.

The point of having call/cc in the language (rather than just call/ec)
is to allow all those control structures that, to the uninitiated,
seem to require time-travel.

> > My point is just that there is a compromise that is superior (if
> > not by much) to a previously discussed compromise. It is superior
> > to Blume's old call/nwc proposal in that it gives call/nwc just as
> > much power while preserving more (but not all) of the properties
> > that dynamic-wind has in a language without call/nwc.
>
> That's fine, but it still is missing the *essential* property of
> dynamic-wind: that it makes a guarantee.

I realize that it does not make the guarantee that you are looking
for, but it does make a guarantee. The guarantee it makes is the one
that Clinger called "the main reason for having dynamic-wind in the
language" (see the first article in this thread). That's why I
considered this new compromise to be an improvement worth mentioning.

-al

Joe Marshall

未读,
2002年7月14日 00:47:192002/7/14
收件人

"David Rush" <ku...@bellsouth.net> wrote in message news:okfptxs...@bellsouth.net...

> "Joe Marshall" <prunes...@attbi.com> writes:
> > "Al Petrofsky" <a...@petrofsky.org> wrote in message news:87wus13...@radish.petrofsky.org...
> > > The idea
> > > that you can simultaneously have full call/cc and something that does
> > > resource cleanup like unwind-protect is a fallacy.
> >
> > I'm not sure I understand what you are saying with this statement,
> > that dynamic-wind won't work in the presence of non-winding continuations
> > (I agree),
>
> Errr, not to butt in or anything, but
>
> "Joe Marshall" wrote in <mrXW8.304476$R61.2...@rwcrnsc52.ops.asp.att.net>:
> > "Ronald Schröder" wrote in message <c23fb729.02070...@posting.google.com>
> > > The FAQ at www.schemers.org also states that a wind-safe call/cc may
> > > be implemented using the wind-unsafe call/cc.
> >
> > And vice versa.
>
> To me this implied that you thought it *was* possible to have both
> call/wc and call/nwc in the same language, particularly in the case
> when call/wc is the primitive operation. Have you changed your
> opinion? Or have I misunderstood you?

I haven't changed my opinion. (In fact, I think I disagree with the FAQ)
But implementing a `wind-unsafe' call-with-current-continuation is easy:
simply don't use dynamic wind.

(define (dynamic-wind before thunk after)
(begin (before) (thunk) (after)))

Now as to the FAQ. If you have the r4rs cwcc, you can write an r5rs cwcc
on top of it using the r4rs cwcc as the primitive mechanism. However,
to guarantee the unwinding, you must guarantee that *all* transfers of
control follow the unwinding semantics.

If you are the implementor of the system, this is not a problem. You
simply shadow or otherwise make the original cwcc inaccessible.

However, if you are *not* the implementor and the system provides only
the r4rs cwcc, you are SOL. You have no way of knowing whether there
is any code in the system that could cause a non-local exit without
running the appropriate thunks.

> I also asked how you would do such a thing w/out undoing R5RS call/cc
> + dynamic-wind in the first place. The only outline implementation
> presented (not by you, Joe) essentially ignored that constraint. Do
> you have a suggestion?

I still don't know what you mean by a `working' dynamic-wind that
doesn't get unwound in certain situations.

John Tobey

未读,
2002年7月14日 22:39:272002/7/14
收件人
On Sun, 14 Jul 2002 00:47:19 -0400, Joe Marshall wrote:

> I still don't know what you mean by a `working' dynamic-wind that
> doesn't get unwound in certain situations.

This complaint is getting tiresome. C++ gives you both kinds, catch
and setjmp (better: swapcontext). (That they are upwards-only does
not affect this argument.) A good C++ reference will warn about the
differences, just as it warns against mixing malloc and delete.
Programmers do not have a problem with this, as far as I know.

Usually, the code that creates a continuation is in the same file as the
code that invokes it, so the caller knows what kind it is. If a module
exports (or imports) continuations via an API, it should document how to
use (or create) them without violating user expectations, just the same
as any API usage is documented.

You write a lot about how dynamic-wind would "break" in the presence
of call/nwc. The only breakage that matters is when users don't get
what they expect. As long as call/wc and dynamic-wind are documented
together in the "Exceptions" chapter, and call/nwc is documented in
the "Coroutines" chapter, and the names call/wc and call/nwc are
changed to be easily distinguishable from each other, all will be
well.

-John

0 个新帖子