CC-delcont- Delimited continuations and dynamically scoped variables

Copyright(c) Amr Sabry, Chung-chieh Shan and Oleg Kiselyov
MaintainerDan Doel
PortabilityNon-portable (generalized algebraic datatypes)
Safe HaskellNone




An implementation of dynamically scoped variables using multi-prompt delimited control operators. This implementation follows that of the paper Delimited Dynamic Binding, by Oleg Kiselyov, Chung-chieh Shan and Amr Sabry (, adapting the Haskell implementation (available at to any delimited control monad (in practice, this is likely just CC and CCT m).

See below for usage examples.


The Dynvar type

data Dynvar m a Source

The type of dynamically scoped variables in a given monad

dnew :: MonadDelimitedCont p s m => m (Dynvar m a) Source

Creates a new dynamically scoped variable

dref :: Dynvar m a -> m a Source

Reads the value of a dynamically scoped variable

dset :: Dynvar m a -> a -> m a Source

Assigns a value to a dynamically scoped variable

dmod :: Dynvar m a -> (a -> a) -> m a Source

Modifies the value of a dynamically scoped variable

dupp :: Dynvar m a -> (a -> m b) -> m b Source

Calls the function, g, with the value of the given Dynvar

dlet :: Dynvar m a -> a -> m b -> m b Source

Introduces a new value to the dynamic variable over a block


The referenced paper provides a full treatment of the behavior of dynamically scoped variables and their interaction with delimited control. However, some examples might provide some intuition. First, a dynamic scoping example:

dscope = do p <- dnew
            x <- dlet p 1 $ f p
            y <- dlet p 2 $ f p
            z <- dlet p 3 $ do z1 <- (dlet p 4 $ f p)
                               z2 <- f p
                               return $ z1 + z2
            return $ x + y + z
 f p = dref p
*Test> runCC dscope

In this example, x = 1, y = 2, z1 = 4 and z2 = 3, even though all come are from reading the same dynamically scoped variable. dlet introduces a scope in which references of the given variable take on a given value. As can be seen, shadowing works properly when writing code in this fashion. In many ways, this is like using the reader monad, with 'dref p' == ask, and 'dlet p v' == 'local (const v)'. The immediate difference, of course, is that you can have multiple dynamic variables instead of the single threaded environment of the reader monad.

Of course, one can also use Dynvars mutably, as in the state monad:

settest = do p <- dnew
             x <- dlet p 1 $ do x1 <- f p
                                dset p 2
                                x2 <- f p
                                return $ [x1, x2]
             y <- dlet p 0 $ do y1 <- f p
                                y2 <- dlet p 1 $ do dset p 3
                                                    f p
                                y3 <- f p
                                return [y1, y2, y3]
             return $ x ++ y
 f p = dupp p return

*Test> runCC settest

So, with analogy to the state monad, 'dref p' == get, and 'dset p v' == 'put v'. Also, as one might expect, such mutations have effects only within the enclosing dlet (and, in fact, an error will result from trying to dset in a scope in which the dynamic var is not bound with dlet). This example also demonstrates the use of the dupp function, to implement the same f function as the first example. Essentially 'dupp p f' = 'dref p >>= f'.

Now, a bit on the interaction between delimited control and dynamic variables. Consider:

test = do p <- dnew
          dlet p 5 (reset (\q -> dlet p 6 (shift q (\f -> dref p))))

*Test> runCC test

In this example, '... reset (q ...' introduces a new delimited context, and '... shift q (f ...' captures that context abortively. This results in the value of 'dref p' being 5, as the 'dlet p 6' resides in the aborted context. Now, consider a slightly more complex example:

test1 = do p <- dnew
           dlet p 5 (reset (\q ->
                        dlet p 6 (shift q (\f ->
                            liftM2 (+) (dref p) (f (dref p))))))

*Test> runCC test1

Here we use 'dref p' twice. Once as before, after we have abortively captured the context, and thus, the outer binding of p is showing. However, the term 'f (dref p)' reinstitutes the captured context for its arguments, and thus, there, 'dref p' takes on a value of 6.

Thus, to sum up, capturing a delimited context captures the dynamic variable bindings *within* that context, but leaves the dynamic bindings *outside* untouched. Similarly, if a context is put back pushed somewhere (for instance, by invoking the function returned by shift, it will put the captured dynamic bindings back in place, but will not restore those dynamic bindings outside of the delimited context (it will, instead, use those visible where the context is invoked.