I'm having difficulty trying to figure out a way to reason about why the following two, seemingly equivalent definitions of an infinite random number sequence (inf
and inf'
) are evaluated completely differently:
import Control.Monad.Random (Rand, evalRandIO, getRandom)
import System.Random (Random, RandomGen, randomIO)
inf :: (RandomGen g, Random a) => Rand g [a]
inf = sequence (repeat getRandom)
inf' :: (Random a) => IO [a]
inf' = sequence (repeat randomIO)
-- OK
main = do
i <- evalRandIO inf
putStrLn $ show $ take 5 (i :: [Int])
-- HANGS
main' = do
i <- inf'
putStrLn $ show $ take 5 (i :: [Int])
when called, main'
terminates and prints 5 random integers, whereas main
loops infinitely — what causes sequence . repeat
to be evaluated differently on getRandom
than it does on randomIO
?
Sequencing lists is strict in the IO monad but possibly lazy in the State monad. Rand
is just a wrapped StateT
, so it can be lazy:
type Rand g = RandT g Identity
newtype RandT g m a = RandT (StateT g m a)
evalRandIO
queries the IO random number generator just once at the beginning, then runs the State
-ful computation on the acquired StdGen
:
evalRandT :: (Monad m) => RandT g m a -> g -> m a
evalRandT (RandT x) g = evalStateT x g
evalRand :: Rand g a -> g -> a
evalRand x g = runIdentity (evalRandT x g)
evalRandIO :: Rand StdGen a -> IO a
evalRandIO x = fmap (evalRand x) newStdGen
In contrast, sequence $ repeat randomIO
contains an infinite number of side effecting IO actions, because each randomIO
modifies the global random number generator. We can't inspect the return value until all the effects are performed. It's similar to doing sequence $ repeat getLine
, which just reads lines repeatedly, and never returns.