Search code examples
haskellmonadsstate-monaddo-notation

What does the put command do in a typical function using the State monad?


This is an example from https://wiki.haskell.org/All_About_Monads It's an example of using State monad to thread the StdGen value through a sequence of random numbers generating commands. If I understand what the last return does correctly it should just create a new monad with x as a value. But then what does the put g' actually do? Why wouldn't the g' actually be lost?

getAny :: (Random a) => State StdGen a
getAny = do g <- get
            (x,g') <- return $ random g
            put g'
            return x

Solution

  • I think you are confused by

    (x, g') <- return $ random g
    

    This indeed create a new monadic action State StdGen (a, StdGen), which is executed to extract its result (a, StdGen).

    The confusion arises with good reason, because the code is actually equivalent to

    let (x, g') = random g
    

    where no monadic action is built, leading to more straightforward code. This transformation is correct in any monad, not just the State one.

    Anyway, the technical part: the (x, g') <- return $ random g snippet means

    (x, g') <- State (\g'' -> (random g, g''))
    

    where we can see that the monadic action takes the current state g'' (which has the same value as g), and then does not modify it (the (..., g'') part) while returning the generated value random g alongside it ((random g, ...) part).

    This is a bit silly, since we do not even need to read g'' since we are using random g!

    So, we are using

    do g       <- State (\g'' -> (g'', g''))
       (x, g') <- State (\g'' -> (random g, g''))
       ...
    

    when we could instead use

    do (x, g') <- State (\g'' -> (random g'', g''))
       ...
    

    which is called in the library

    do (x, g') <- gets random
       ...
    

    Okay, the confusion appears to be in do put g' ; return x. This is desugared into bind-notation as follows

    { definitions }
    put g'   = State $ \s -> ((), g')
    return x = State $ \s -> (x , s )
    
    do put g ; return x 
    = { definitions, desugaring }
       (State $ \s -> ((), g'))
       >>= 
       (\_ -> State $ \s -> (x , s ))
    = { definition of >>= }
       State $ \s -> let (v,s') = (\s -> ((), g')) s 
                     in runState ((\_ -> State $ \s -> (x , s )) v) s'
    = { beta-reduction (application) }
       State $ \s -> let (v,s') = ((), g')
                     in runState (State $ \s -> (x , s )) s'
    = { beta-reduction (let) }
       State $ \s -> runState (State $ \s -> (x , s )) g'
    = { runState (State y) = y }
       State $ \s -> (\s -> (x , s )) g'
    = { beta-reduction }
       State $ \s -> (x , g')
    

    So, the effect of do put g' ; return x is to modify the state to g' (overwriting the previous one s) and to yield x as a final value of the computation (alongside g').