{-# LANGUAGE GADTs #-} ------------------------------------------------------------------------------- -- | -- Module : Control.Monad.CC.Dynvar -- Copyright : (c) Amr Sabry, Chung-chieh Shan and Oleg Kiselyov -- License : MIT -- -- Maintainer : Dan Doel -- Stability : Experimental -- Portability : Non-portable (generalized algebraic datatypes) -- -- 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. module Control.Monad.CC.Dynvar ( -- * The Dynvar type Dynvar(), dnew, dref, dset, dmod, dupp, dlet, module Control.Monad.CC -- * examples -- $examples ) where import Control.Monad import Control.Monad.CC -- | The type of dynamically scoped variables in a given monad data Dynvar m a where Dynvar :: MonadDelimitedCont p s m => p (a -> m a) -> Dynvar m a -- | Creates a new dynamically scoped variable dnew :: MonadDelimitedCont p s m => m (Dynvar m a) dnew = Dynvar `liftM` newPrompt -- | Reads the value of a dynamically scoped variable dref :: Dynvar m a -> m a dref (Dynvar p) = shift p (\f -> return $ \v -> f (return v) >>= ($ v)) -- | Assigns a value to a dynamically scoped variable dset :: Dynvar m a -> a -> m a dset (Dynvar p) newv = shift p (\f -> return $ \v -> f (return v) >>= ($ newv)) -- | Modifies the value of a dynamically scoped variable dmod :: Dynvar m a -> (a -> a) -> m a dmod p@(Dynvar _) f = dref p >>= dset p . f -- | Calls the function, g, with the value of the given Dynvar dupp :: Dynvar m a -> (a -> m b) -> m b dupp p@(Dynvar _) g = dref p >>= g -- | Introduces a new value to the dynamic variable over a block dlet :: Dynvar m a -> a -> m b -> m b dlet (Dynvar p) v body = reset (\q -> pushPrompt p (body >>= (\z -> abort q (return z))) >>= ($ v) >>= undefined) ------------------------------------------------------------------------------- -- $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.