[closer-devel] Adding dynamic-wind to ContextL

Pascal Costanza pc at p-cos.net
Sat Dec 27 11:13:24 UTC 2008


Hi everybody,

I hope you all had a nice Christmas. And all the best for 2009! ;)

I have spent the last couple of weeks on designing an interface for  
supporting first-class dynamic environments. This appears to be more  
complex than I initially thought - recording the values of (special)  
symbols representing dynamic variables is clearly not enough.

I have stumbled over the following problem: In ContextL, the  
activation of layers amounts to roughly something like the following.  
There is a global *current-context* variable that records all the  
active layers as a list. So each layer activation corresponds to  
something like the following (strongly simplified).

(let ((*current-context* (cons activated-layer *current-context*)))
   ...)

Since I want to ensure that ContextL plays nice with the various  
Common Lisp frameworks for supporting first-class continuations, and  
since they actually implement partial continuations, I need a way to  
mark a position in the current control flow, and when I capture the  
current dynamic environment, I should actually only record those layer  
activations that were activated since a certain given mark. [1] I  
could theoretically record the mark in the *current-context* - so  
whenever a mark is set, I should also ensure that this is triggered  
for *current-context*:

(let ((*current-context* (cons some-mark *current-context*)))
   ...)

I could then search for that mark when capturing dynamic environments  
and only capture the "lower" part of the *current-context*. However,  
this doesn't work for deactivating layers. Deactivating layers looks  
roughly as follows:

(let ((*current-context* (remove deactivated-layer *current-context*)))
   ...)

There may be a way to mingle the layers and the marks in such a way  
that I can still get the right result, but this gets really messy. (I  
would have to record layer deactivations explicitly, by storing some  
kind of deactivation record in the current context, or so.)

After some more thought about this whole problem, I came to the  
conclusion that providing a form of dynamic-wind, like in Scheme, is  
the better generalisation of dynamic environments. [2] So I plan to  
add the following to ContextL:


+ A with-dynamic-mark macro, that allows delimiting potential dynamic  
environments. The macro can be used like this:

(with-dynamic-mark (some-mark-variable)
    some code)

with-dynamic-mark delimits the current dynamic environment with a  
mark, and binds the some-mark-variable to a first-class representation  
of that mark (of unspecified type). Some-mark-variable can be  
lexically or dynamically scoped, which gives you static or dynamic  
delimited environments (corresponding to static or dynamic delimited  
continuations).


+ A dynamic-wind macro that allows setting up code that gets executed  
whenever a dynamic environment is (re)established. It is very roughly  
like Scheme's dynamic-wind, except that it isn't a function but a  
macro, that it doesn't provide the unwind-protect functionality (that  
would be redundant), and that it doesn't provide the set-up, main  
body, and tear-down code as separate thunks. The macro can be used  
like this:

(dynamic-wind
   (let ((*current-context* (cons something *current-context*)))
     (proceed
       some code)))

The code that is captured by dynamic-wind will just be executed as  
normal, but whenever a dynamic environment is reestablished that  
captures this dynamic-wind, the code here is re-executed up until the  
proceed form, which is then replaced with the continuation of  
reestablishing that dynamic environment. Proceed is like a progn,  
except that it marks the variable part of a dynamic-wind.

You can combine this with unwind-protect:

(let ((cell 42))
   (dynamic-wind
     (let ((*some-variable* cell))
       (unwind-protect
            (proceed with-something)
         (setq cell *some-variable*)))))

This example ensures that *some-variable* is always bound to a given  
value in some dynamic environment, and also ensures that side effects  
to *some-variable* are handled correctly.

A Scheme dynamic-wind, like this:

(dynamic-wind set-up body tear-down)

corresponds to a ContextL dynamic-wind as follows:

(dynamic-wind
   (funcall set-up)
   (unwind-protect
       (proceed (funcall body))
     (funcall tear-down)))

[An advantage of this design of dynamic-wind is that I can completely  
remove the recording of dynamic-wind contexts if a client sets a flag  
that it doesn't want support for first-class dynamic environments.]


+ A capture-dynamic-environment function that allows capturing the  
current dynamic environment, either completely or up unto a certain  
dynamic mark. The function can be used like this:

(capture-dynamic-environment mark)

This returns a first-class representation of the dynamic environment  
up until the mark. If you want the complete dynamic environment, you  
can leave out the mark:

(capture-dynamic-environment)

The dynamic environment basically consists of a list of dynamic-wind  
thunks that can be reestablished with the with-dynamic-environment  
macro below.


+ Finally, a with-dynamic-environment macro that allows reestablishing  
a dynamic environment. You can use with-dynamic-environment as follows:

(with-dynamic-environment (environment)
   ...)

It calls all the dynamic-wind thunks in environment (leaving out the  
proceed parts), and then continues execution with the body of with- 
dynamic-environment.


If you want to have support for dynamic-wind in a call/cc library, you  
can use with-dynamic-mark whenever you start a call/cc context, use  
dynamic-wind everywhere, use capture-dynamic-environment in  
conjunction with call/cc, and use with-dynamic-environment when you  
invoke a continuation (or put it in the continuation yielded by call/ 
cc so that this happens automatically).

I will furthermore make sure that all dynamically scoped binding forms  
in ContextL (like with-active-layers, with-inactive-layers, etc.) are  
re-implemented using dynamic-wind.


Any comments on this design? Are there any holes you can notice that I  
might have missed? Most of the functionality is provided as macros -  
do you think that's ok, or should I rather go for functional versions?  
(I would strongly prefer dynamic-wind as a macro, to have better  
optimization opportunities, but for the other operators, I am willing  
to negotiate. ;)


Please let me know what you think. I'm not online for the next few  
days, but will hopefully have time again next week to implement this...



Best,
Pascal

P.S.: I now know what bothered me in Scheme's dynamic-wind / call/cc  
approach: Dynamic-wind is a feature to support dynamic environments,  
whereas call/cc is a feature to support continuations. The combination  
overloads one construct with two different semantic concepts. It's  
true that most of the time you want both combined, but the combination  
seems forced to me. Dynamic-wind should be part of a library for  
supporting dynamic scoping, and call/cc should be left untouched. This  
should also resolve the controversy around dynamic-wind in the Scheme  
community. Not that I really care that much... ;-)



[1] For a better explanation of this, see the excellent paper about  
Delimited Dynamic Binding at http://okmij.org/ftp/Computation/dynamic-binding.html#DDBinding 
  - especially the sections 4.2.1 to 4.2.3.

[2] So Attila was right. ;)

-- 
Pascal Costanza, mailto:pc at p-cos.net, http://p-cos.net
Vrije Universiteit Brussel, Programming Technology Lab
Pleinlaan 2, B-1050 Brussel, Belgium












More information about the closer-devel mailing list