Search code examples
haskellmonadsmonad-transformersstate-monadio-monad

Run this monadic computation with notion of state and randomness


I describe the following computation:

import Control.Monad.State
import Control.Monad.Identity
import Control.Monad.Random.Class

-- * fair coin
fair :: MonadRandom m => m Bool
fair = (\p -> p <= 0.5) <$> getRandomR (0,1 :: Double)

-- * how do i run this 
bar :: (MonadState Bool m, MonadRandom m) => m Bool
bar = fair >>= \b -> put b >> return b

And I would like to know how to run bar so that in expectation, bar evaluates to True half of the time.


Solution

  • In short:

    evalRandIO $ evalStateT bar False
    

    Let’s pick this apart. bar needs to be able to maintain a Bool as a state, and be able to get random values. We could nest it either way; here, I chose to layer the state atop the random, but you could do it the other way around.

    evalStateT has this type signature:

    evalStateT :: Monad m => StateT s m a -> s -> m a
    

    In words, given something that needs a state layered atop some other monad, it implements that state part and gives you that action in terms of the underlying monad. The eval part of the name means that it will throw out the resulting state and give you just the value; there is also execStateT that gives you the resulting state and throws out the output, and runStateT that gives you a tuple of both. I should note that in any case, we have to give it an initial state. Your code doesn’t use the initial state, so we could just as well have used undefined, but I used False for the heck of it.

    So now that we’ve implemented the state bit, what do we have left?

    ghci> :t evalStateT bar False
    evalStateT bar False :: MonadRandom m => m Bool
    

    It wants a monad that can give it random values. Well, we have one of those. Rand will do it. Rand, too, has run, eval, and exec variants, since it’s actually a state monad too; it holds a value of some type of class RandomGen under the covers. Here, we don’t want to discard the state at the end, and we do want to keep the result, too, so we use the run variant. Well, what do we have now?

    ghci> :t runRand (evalStateT bar False)
    runRand (evalStateT bar False) :: RandomGen g => g -> (Bool, g)
    

    For comparison:

    ghci> :t random
    random :: (RandomGen g, Random a) => g -> (a, g)
    

    So now we have a plain function that takes a random-number generator state and spits out a pair with a result and a new state, just like random itself from System.Random. How can we use this? Well, digging through the System.Random module, getStdRandom looks useful:

    getStdRandom :: (StdGen -> (a, StdGen)) -> IO a
    

    It takes a function like the one we have and turns it into an IO action. (This makes sense that it would be IO; it’s taking some global state (namely, the standard random number generator) and updating it.) What do we have after tacking that on?

    ghci> :t getStdRandom . runRand $ evalStateT bar False
    getStdRandom . runRand $ evalStateT bar False :: IO Bool
    

    Exactly what we expect: an IO that yields a Bool. Run it a few times:

    ghci> let barIO = getStdRandom . runRand $ evalStateT bar False
    ghci> barIO
    True
    ghci> barIO
    True
    ghci> barIO
    False
    

    Random enough for me. However, as Carsten mentions in the comments, there’s a shorter way. Since this is such a common use-case, the Control.Monad.Random module provides a shortcut, evalRandIO, which is essentially the getStdRandom . runRand we used above. It’s a simple replacement to end up with

    evalRandIO $ evalStateT bar False