[plt-scheme] composable continuations and dynamic state
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)
Ryan