Copyright | (c) Amr Sabry, Chung-chieh Shan and Oleg Kiselyov |
---|---|
License | MIT |
Maintainer | Dan Doel |
Stability | Experimental |
Portability | Non-portable (generalized algebraic datatypes) |
Safe Haskell | None |
Language | Haskell98 |
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 (http://okmij.org/ftp/papers/DDBinding.pdf), adapting the Haskell implementation (available at http://okmij.org/ftp/packages/DBplusDC.tar.gz) to any delimited control monad (in practice, this is likely just CC and CCT m).
See below for usage examples.
The Dynvar type
dnew :: MonadDelimitedCont p s m => m (Dynvar m a) Source
Creates a new 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
module Control.Monad.CC
examples
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 where f p = dref p
*Test> runCC dscope 10
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 where f p = dupp p return *Test> runCC settest [1,2,0,3,0]
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 5
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 11
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.