Question

Written in this handy do-notation, this seems pretty clear to me. But I can't seem to get the de-sugared version using >>= to work which is worrisom. Could someone re-write these in expanded notation please?

Not nested

stack1 :: StateT Int Identity ( Int, Int )
stack1 = do
    a <- get 
    put ( a + 1 )
    b <- get
    return ( a, b )

runstack1 :: ( Int, Int )
runstack1 = evalState stack1 11

Nested

stack3 :: StateT Int ( StateT String ( StateT String Identity ) ) ( Int, String, String )
stack3 = do
    modify (+10)
    lift $ modify ( ++ " world" )
    lift . lift $ modify ( ++ " word" )
    a <- get
    b <- lift get
    c <- lift . lift $ get
    return ( a, b, c )


runStack3 :: ( Int, String, String )
runStack3 = runIdentity $ evalStateT ( evalStateT ( evalStateT runStack3 1 ) "hello" ) "harro"

Additionally, by comparing at the signature of runStack1 and runStack3 I can see why runIdentity is needed, but could someone explain the internals of why this is, since both stack1 and stack3 are wrapping Identity constructor?

Was it helpful?

Solution

Desugared version of the nested example:

stack4 :: StateT Int ( StateT String ( StateT String Identity ) ) ( Int, String, String )
stack4 = modify (+10) >>= \_ ->
         (lift $ modify ( ++ " world" )) >>= \_ ->
         (lift . lift $ modify ( ++ " word" )) >>= \_ ->
         get >>= \a ->
         lift get >>= \b -> 
         (lift . lift $ get) >>= \c ->
         return (a,b,c)

In applicative style:

import Control.Applicative

stack5 :: StateT Int ( StateT String ( StateT String Identity ) ) ( Int, String, String )
stack5 = modify (+10) *> 
         (lift $ modify ( ++ " world" )) *>
         (lift . lift $ modify ( ++ " word" )) *>  
         ((,,) <$> get <*> lift get <*> (lift . lift $ get))

Also, Lambdabot can perform automatic desugaring, see this question.

As for the need for runIdentity, there's nothing mysterious about that. You have to unwrap every layer of a monad stack to get to the value inside, and Identity happens to be in the stack. Now, the State monad can be implemented in terms of StateT and Identity, but in that case the user is presented an "unified view" which hides the inner machinery. If you check the source code for runState in the transformers package, you'll see that it calls runIdentity internally.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top