Home > Archive > Functional > May 2006 > Looking for basic state transformer example
You are viewing an archived Text-only version of the thread.
To view this thread in it's original format and/or if you want to reply to
this thread please [click here]
| Author |
Looking for basic state transformer example
|
|
| Dan Piponi 2006-05-07, 7:13 pm |
| I have a situation where I need to interleave computations in two
monads and I think that I need to use monad transformers to do this.
Unfortunately I'm finding the literature on the subject opaque and the
examples a little complicated. Could someone help me with a very
elementary example to get me started?
Here are two uses of a state monad, one with an Integer and one with a
String as state:
> import Control.Monad.State
>
> test1 = do
> a <- get
> modify (+1)
> b <- get
> return (a,b)
>
> test2 = do
> a <- get
> modify (++"1")
> b <- get
> return (a,b)
>
> go1 = runState test1 0
> go2 = runState test2 "0"
They work fine. But now I'd like to combine them into a piece of code
that looks something like this:
> test3 = do
> modify (+1) -- update Integer state
> modify (++"1") -- update String state
> a <- get -- get Integer state
> b <- get -- get String state
> return (a,b) -- should return (1,"01") if states initialised with 0 and "0"
go3 = runState test3 ... -- initialize both states somehow
This clearly isn't legal Haskell code. But what do I need to do to make
it legal and correspond to my comments (besides the obvious thing of
manually constructing a State monad where the state is a pair of an
Integer and a String).
--
Dan
| |
| Lutz Donnerhacke 2006-05-07, 7:13 pm |
| test3 = do
modify (\(x,y) -> (x+1,y)) -- update Integer state
modify (\(x,y) -> (x,y++"1") -- update String state
get
| |
| Dan Piponi 2006-05-07, 7:13 pm |
| Lutz replied,
> test3 = do
> modify (\(x,y) -> (x+1,y)) -- update Integer state
> modify (\(x,y) -> (x,y++"1") -- update String state
> get
That works. But that's what I'd call the manual approach. How can I
combine the two separate states 'automatically' using monad
transformers?
| |
| Lutz Donnerhacke 2006-05-07, 7:13 pm |
| * Dan Piponi wrote:
> Lutz replied,
>
> That works. But that's what I'd call the manual approach. How can I
> combine the two separate states 'automatically' using monad
> transformers?
Beside playing with "class", your intend is not valid: You assume that
different positional arguments can be determined soley by their type.
How will you "automatically" update the "right" String state,
if your state contains more than one String?
You might think about type combiners and therefore end up with:
test4 = do
modify (doLeft (+1)) -- update Integer state
modify (doRight (++"1")) -- update String state
get
doLeft f (x,y) = (f x, y)
doRight f (x,y) = (x, f y)
If you rearrange this you conclude with:
test5 = do
modifyLeft (+1) -- update Integer state
modifyRight (++"1") -- update String state
get
And you are done. *grin*
| |
| Dan Piponi 2006-05-07, 7:13 pm |
| Lutz said:
> test5 = do
> modifyLeft (+1) -- update Integer state
> modifyRight (++"1") -- update String state
> get
Maybe I didn't make my intention clear. I wasn't expecting to write
literally that code but something like it with the comments indicating
the kind of changes I expected.
Anyway, as always, I spend ages trying to figure out what I want but
when I post to usenet I finally solve it for myself. Here is the piece
of code I wanted:
> import Control.Monad.State
> import Control.Monad.Identity
>
> test = do
> modify (+ 1)
> lift $ modify (++ "1")
> a <- get
> b <- lift get
> return (a,b)
>
> go = runIdentity $ evalStateT (evalStateT test 0) "0"
That was much simpler than any example I managed to find on the web. By
using lift I can choose which of the two states I want to read or
write.
--
Dan
| |
| Dinko Tenev 2006-05-07, 7:13 pm |
| Dan Piponi wrote:
[ ... ][color=darkred]
[ ... ]
That means a lot of lifting for complex combinations of states -- usage
will get cumbersome if you have to chain 5 or more transformers this
way. Another fly in the ointment is that it is not symmetric with
respect to the states being combined. It's a rather clever device
though -- thanks for sharing it.
For what it's worth, I've considered Template Haskell for designing a
solution, but never needed one so badly as to actually go through the
pain of implementing it :)
Cheers,
Dinko
| |
| Lauri Alanko 2006-05-07, 7:13 pm |
| In article <1146765891.767210.65370@j73g2000cwa.googlegroups.com>,
Dan Piponi <google03@sigfpe.com> wrote:
> They work fine. But now I'd like to combine them into a piece of code
> that looks something like this:
>
> 0 and "0"
You can do this by losing the functional dependencies in MonadState
and allowing overlapping instances. However, this quickly results in
an unmaintainable mess and you have to provide type annotations
everywhere. But for what it's worth, here's an example. Try it with
ghci -fglasgow-exts -fallow-overlapping-instances -fallow-incoherent-instances
import Control.Monad
import Control.Monad.State
import Control.Monad.Identity
class Monad m => MultiState m s where
getState :: m s
putState :: s -> m ()
instance Monad m => MultiState (StateT s m) s where
getState = get
putState = put
instance MultiState m s => MultiState (StateT s' m) s where
getState = lift getState
putState s = lift (putState s)
updateState f = do s <- getState
putState (f s)
testState :: (MultiState m Int, MultiState m String) => m (Int, String)
testState =
do updateState (+ (1 :: Int))
updateState (++ "1")
a <- getState
b <- getState
return (a, b)
type IntStringS = StateT Int (StateT String Identity)
runTest =
runIdentity $
evalStateT (evalStateT (testState :: IntStringS (Int, String)) 0)
"0"
Hope this helps.
Lauri
| |
| Dan Piponi 2006-05-07, 7:13 pm |
| Dinko said:
> usage will get cumbersome if you have to chain 5 or more transformers
That's what I thought when I started looking into this recently. (To be
honest, I was a little scared by monad transformers.) But having now
figured it out I'm surprised to find it isn't bad at all. I immediately
applied it to some code of my own where conceptually I needed to
combine 3 monads but in fact I had used manually constructed glue to
get everything working. Armed with this new knowledge most of the
changes I had to make to my code were in fact deletions of the glue.
The lines in my code that I had to add look no more cumbersome than
this sort of thing:
num <- get -- State monad
modify (+1) -- State again
name <- lift ask -- Reader monad
lift $ lift $ tell ...stuff... -- Writer monad
And even more amazingly, because in my case the three monads are
different it turns out I can dump the lifts and simply write:
num <- get
modify (+1)
name <- ask
tell ...stuff...
The monad transformers have been written in such a way that for some
types of inner monad the interface of the inner monad is exposed in the
outer one, regaining some kind of symmetry. Unfortunately I didn't find
this terribly well documented, which is a pity because I think the
author of the monad transformer library did a great job of making it
easy to use once you actually figure it all out.
(Not sure I approve of the implicit lifting thing either, I can see it
might cause some problems...)
--
Dan
| |
| Dinko Tenev 2006-05-08, 8:02 am |
| Dan Piponi wrote:
> Dinko said:
>
>
> That's what I thought when I started looking into this recently. (To be
> honest, I was a little scared by monad transformers.) But having now
> figured it out I'm surprised to find it isn't bad at all. I immediately
[ ... ]
>
> num <- get -- State monad
> modify (+1) -- State again
> name <- lift ask -- Reader monad
> lift $ lift $ tell ...stuff... -- Writer monad
I'd call this bad enough :) You end up identifying your monads by the
"arity" of the lift, that is, by their position in the transformer
chain (1, 2, 3, etc.), only worse -- brrrrr...
> And even more amazingly, because in my case the three monads are
> different it turns out I can dump the lifts and simply write:
>
> num <- get
> modify (+1)
> name <- ask
> tell ...stuff...
And *that* would be even scarier, if only I could reproduce it :) The
lifted version above loads fine, but this last one simply won't. Could
you post a standalone source, if that's not too much to ask? (I am
growing all the more curious about this)
Cheers,
Dinko
| |
| Dan Piponi 2006-05-08, 10:05 pm |
| Dinko said:
> You end up identifying your monads by the "arity" of the lift
You could always add some sweetener:
> test3 = do
> classA $ modify (+ 1)
> classB $ modify (++ "1")
> classC $ put 3.14159
> a <- classA get
> b <- classB get
> c <- classC get
> return (a,b,c) where
> classA = id
> classB = lift
> classC = lift . lift
> Could you post a standalone source
> import Control.Monad.State
> import Control.Monad.Identity
> import Control.Monad.Reader
> test7 = do
> modify (++ ", world")
> a <- ask -- from reader monad with no lift!
> b <- get -- from state monad
> return (a,b)
> go7 = runReader (evalStateT test7 "hello") 666
Works with GHC 6.4.1
The fact that you can omit lift was something that I actually managed
to deduce from the documentation here:
http://www.haskell.org/ghc/docs/6.4...onad-State.html
It's pretty cryptic. For example:
MonadReader r m => MonadReader r (StateT s m)
says that a StateT transformer containing a MonadReader also exposes
the MonadReader interface. (I think.)
--
Dan
| |
| Dinko Tenev 2006-05-09, 4:26 am |
| Dan Piponi wrote:
>
>
>
> Works with GHC 6.4.1
Right. My problem seems to have been my using 6.4, combined with the
lack of explicit signatures -- for some reason, 6.4 can't infer the
types for the above (or maybe the types it was trying to infer were
"too polymorphic" -- beats me.)
> The fact that you can omit lift was something that I actually managed
> to deduce from the documentation here:
> http://www.haskell.org/ghc/docs/6.4...onad-State.html
> It's pretty cryptic. For example:
> MonadReader r m => MonadReader r (StateT s m)
> says that a StateT transformer containing a MonadReader also exposes
> the MonadReader interface. (I think.)
It does indeed...for those who have cared to read it ;)
Thanks again for the pointers.
Cheers,
Dinko
|
|
|
|
|