[plt-scheme] composable continuations and dynamic state

From: Ryan Culpepper (ryanc at ccs.neu.edu)
Date: Thu Jun 4 02:04:43 EDT 2009

Taylor R Campbell wrote:
> In the following test of the interaction between dynamic state and
> composable continuations, I observed a difference between dynamic
> bindings established through parameters and PARAMETRIZE, and dynamic
> bindings established through DYNAMIC-WIND.  With DYNAMIC-WIND the test
> behaves as I expect; with PARAMETRIZE it does not.
> [snip definition of test]
> Below are the implementations of the arguments MAKE-PARAMETER,
> PARAMETER-VALUE, and WITH-PARAMETRIZATION, but first, a summary of
> results:
>      parametrize                 dynamic-wind
>    (1 a0 initial-b)            (1 a0 initial-b)
>    (2 a0 b0)                   (2 a0 b0)
>    (3 a0 initial-b)            (3 a0 initial-b)
>    (4 initial-a initial-b)     (4 initial-a initial-b)
>    (5 a0 b0)                   (5 initial-a b0)
>    (6 a0 b0)                   (6 a1 b0)
>    (7 a0 b0)                   (7 initial-a b0)
> The following definitions are all obvious.  Both naive and sensible
> DYNAMIC-WIND implementations yield identical output.  I am aware that
> there is an extra layer of indirection in the use of parameters, since
> continuation marks map parameters to thread cells, which threads map
> to values, and PARAMETRIZE only binds the parameters to fresh thread
> cells in the continuation marks.  However, since there is only dynamic
> binding and no mutation in the above TEST procedure, I think that
> every thread cell in question will only ever have a single value.
> (This is the same reason for why the naive and sensible DYNAMIC-WIND
> implementations yield identical output.)

The thread cell indirection is, as you surmised, totally irrelevant to 
the results. You're seeing the effects of another indirection, the 
"parameterization". Parameter-value associations are not stored 
individually in the continuation. Instead, the continuation contains 
"parameterizations", or mappings of all parameters to their current 
values (really, to their currently associated thread-cells). 
Consequently, when you separate a continuation into parts, you do *not* 
separate the parameter updates belonging to those parts.

Here's a re-implementation of parameters, without the 
mutation/thread-cell behavior. You'll need to know about PLT Scheme's 
continuation marks to follow; they're basically special annotation 
frames that you can drop onto your context (using 
'with-continuation-mark') and read (here, using 
'continuation-mark-set-first'). Note that 'hash-set' is a functional 
update on an immutable mapping.

   ;; A (Param-of X) is (make-param X)
   (define-struct param (init))

   ;; A Parameterization is (hash-of (exists X) (Param-of X) => X)

   ;; param-value : (Param-of X) -> X
   (define (param-value p)
     (let ([paramz (get-paramz)])
       (hash-ref paramz p (lambda () (param-init p)))))

   ;; get-paramz : -> Parameterization
   (define (get-paramz)
     (or (continuation-mark-set-first #f 'paramz)
         (make-immutable-hasheq null)))

   ;; call-with-param : (Param-of X) X (-> Y) -> Y
   (define (call-with-param p v k)
     (with-continuation-mark 'paramz (hash-set (get-paramz) p v) (k)))

   (display "-- parameters\n")
   (test make-param param-value call-with-param)

Here's an implementation of individually-varying "dynamic variables". 
They have the same behavior as 'dynamic-wind' in your tests.

   ;; A (Dynamic-of X) is (make-dynamic X)
   (define-struct dynamic (init))

   ;; dynamic-value : (Dynamic-of X) -> X
   (define (dynamic-value d)
     (let ([v (continuation-mark-set-first #f d)])
       (if v (car v) (dynamic-init d))))

   ;; call-with-dynamic : (Dynamic-of X) X (-> Y) -> Y
   (define (call-with-dynamic d v k)
     (with-continuation-mark d (list v) (k)))

   (display "-- dynamic variables\n")
   (test make-dynamic dynamic-value call-with-dynamic)


Posted on the users mailing list.