[Haskell-cafe] Stacking StateTs
Luis O'Shea
loshea at gmail.com
Sat Feb 21 15:33:28 EST 2009
I've been experimenting with the state monad and with StateT, and
have some questions about how to combine one state with another.
This email is literate Haskell tested on GHCi, version 6.10.1. Also,
sigfpe's post on monad transformers (http://blog.sigfpe.com/2006/05/
grok-haskell-monad-transformers.html) was very helpful.
> import Control.Monad.State
My question is basically whether the function modifyT (below) makes
sense, whether some form of it already exists in a standard library,
and (most importantly) whether it actually indicates that I'm
thinking about StateT all wrong.
> modifyT :: Monad m =>
> (s -> StateT t m s)
> -> StateT t (StateT s m) ()
> modifyT f = do
> x <- get
> y <- lift get
> (y',x') <- lift $ lift $ runStateT (f y) x
> lift $ put y'
> put x'
Some context may be useful, so here is how I ended up thinking I
needed modifyT.
The state monad makes it easy to write stateful computations. For
example here is a computation that has an Integer as its state and
returns a String:
> test1 :: State Integer String
> test1 = do
> modify (+ 1)
> a <- get
> return $ "foobar" ++ (show a)
If the computation wants to do some IO then it makes sense to start
with the IO monad and then apply the StateT transformer to it:
> test2 :: StateT Integer IO String
> test2 = do
> modify (+ 1)
> a <- get
> lift $ print a
> return $ "foobar" ++ (show a)
So from now on I won't actually do any IO and will replace IO with an
arbitrary monad m. Also instead of the fixed string "foobar" I'll
have it take a String as a parameter:
> test3 :: Monad m => String -> StateT Integer m String
> test3 s = do
> modify (+ 1)
> a <- get
> return $ s ++ (show a)
A nice feature of all this is that it is easy to combine these
computations:
> test4 :: Monad m => StateT Integer m (String,String)
> test4 = do
> s1 <- test3 "foo"
> s2 <- test3 "bar"
> return $ (s1,s2)
Now seeing as test3 takes a String and returns another String you can
imagine using it to transform a String state. (I'm also going to
assume that test3 is in another library so we don't want to alter how
it's written.) So here is how you could use test3 in a computation
that has (String,Integer) as its state:
> test5 :: (Monad m) => m Integer
> test5 = do
> (s1,x1) <- runStateT (test3 "") 0
> (s2,x2) <- runStateT (test3 s1) (2*x1 + 1)
> (s3,x3) <- runStateT (test3 s2) (x2*x2)
> return x3
Then running test5 >>= print gives 17. The problem with test5, of
course, is that we have manually threaded the state, with all the
problems that implies. For example nothing prevents you from
erroneously misthreading the state:
> test5bad :: (Monad m) => m Integer
> test5bad = do
> (s1,x1) <- runStateT (test3 "") 0
> (s2,x2) <- runStateT (test3 s1) (2*x1 + 1)
> (s3,x3) <- runStateT (test3 s1) (x2*x1)
> return x3
Running test5bad >>= print gives 5. Obviously we want operate in a
State monad with more state. One way to do this is to stack two
StateTs on top of m. This is, finally, where I need the modifyT that
we defined above -- it lets us "lift" test3 to a function that
modifies the state of the top *two* StateTs. Now let's use it to
rewrite test5:
> test6 :: (Monad m) => StateT Integer (StateT String m) Integer
> test6 = do
> modifyT test3
> modify $ \x -> 2*x + 1
> modifyT test3
> modify $ \x -> x*x
> modifyT test3
> x <- get
> return x
>
> test7 :: (Monad m) => m Integer
> test7 = evalStateT (evalStateT test6 0) ""
As expected, running test7 >>= print gives 17.
So, given that modifyT seems to be useful, does it, or something like
it, already exists in the standard libraries? More likely, am I
making a mountain of a molehill and is there a better way to
structure all this?
Thanks,
Luis
> main = do
> test5 >>= print
> test5bad >>= print
> test7 >>= print
More information about the Haskell-Cafe
mailing list