yield based on delimited continuations in TXR Lisp, showing that unwind-protect works: (defun grandkid ()
(unwind-protect
(yield-from parent 'in-grandkid)
(put-line "returning from grandkid")))
(defun kid ()
(unwind-protect
(progn
(yield-from parent 'in-kid)
(grandkid))
(put-line "returning from kid")))
(defun parent ()
(unwind-protect
(progn
(yield-from parent 'in-parent)
(kid))
(put-line "returning from parent")))
(let ((fn (obtain (parent))))
(prinl 'a)
(prinl (call fn))
(prinl 'b)
(prinl (call fn))
(prinl 'c)
(prinl (call fn))
(prinl 'd)
(prinl (call fn))
(prinl 'e))
Run: $ txr cont.tl
a
in-parent
b
in-kid
c
in-grandkid
d
returning from grandkid
returning from kid
returning from parent
nil
e
Each yield captures a new delimited continuation up to the parent prompt. Each (call fn) dispatches a new continuation to continue on to the next yield, or termination.So with all this back-and-forth re-entry, why do the unwind-protects just go off once? Because of a mechanism that I call "absconding": the answer to the dynamic wind problem. Absconding is a way of performing a non-local dynamic control transfer without triggering unwinding. It's much like the way, say, the C longjmp is unaware of C++ destructors: the longjmp absconds past them. With absconding we can get out of the scope where we have resumed a continuation without disturbing the scoped resources which that context needs. Then with the continuation we captured, we can go back there. Everything dynamic is intact. Dynamically scoped variables, established exception handlers, you name it. The regular function returns are not absconding so they trigger the unwind-protect in the normal way. absconding is an elephant gun, that should only be used in the implementation of primitives like obtain/yield. 15 second tutorial: Cleanup yes: 1> (block foo (unwind-protect (return-from foo 42) (prinl 'cleanup)))
cleanup
42
Cleanup no: 2> (block foo (unwind-protect (sys:abscond-from foo 42) (prinl 'cleanup)))
42
That's all there is to absconding. yield-from uses it. It captures a new continuation, packages it up and absconds to the prompt, where that is unpacked, the new continuation updated in place of the old so that fn will next dispatch that new one. |