-->

Infinite random sequence loops with randomIO but n

2019-06-08 07:32发布

问题:

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?

回答1:

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.