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 wrappedStateT
, so it can be lazy:evalRandIO
queries the IO random number generator just once at the beginning, then runs theState
-ful computation on the acquiredStdGen
:In contrast,
sequence $ repeat randomIO
contains an infinite number of side effecting IO actions, because eachrandomIO
modifies the global random number generator. We can't inspect the return value until all the effects are performed. It's similar to doingsequence $ repeat getLine
, which just reads lines repeatedly, and never returns.