Search code examples
listhaskellrandominfinite

generate infinite list of RandomGen


I want to apply a function f which needs a RandomGen over a list. I tried to generate me therefore a infinite list of RandomGen as you can see below. (just random values as generated by the function "randoms" isn't sufficient, because the needed range of the value depends on the input for f.)

module Test where
import System.Random (getStdGen, RandomGen, randomR, random)

f :: (RandomGen g, Integral a) => g -> a -> Bool

randomGens :: RandomGen g => g -> [g]
randomGens gen =
  let (i, gen') = (random gen) :: (Int, g1)
  in  gen : (repeatGen gen')

Unfortunately the compiler tells me, that it fails

Test.hs:13:26:
    Could not deduce (g1 ~ g)
    from the context (RandomGen g)
      bound by the type signature for
             randomGens :: RandomGen g => g -> (g, g)
      at Test.hs:11:14-39
    or from (RandomGen g1)
      bound by an expression type signature: RandomGen g1 => (Int, g1)
      at Test.hs:13:19-55
      `g1' is a rigid type variable bound by
           an expression type signature: RandomGen g1 => (Int, g1)
           at Test.hs:13:19
      `g' is a rigid type variable bound by
      the type signature for randomGens :: RandomGen g => g -> (g, g)
          at Test.hs:11:14
    In the first argument of `random', namely `gen'
    In the expression: random gen :: RandomGen g => (Int, g)
    In a pattern binding:
      (i, gen') = random gen :: RandomGen g => (Int, g)

Just skipping in the let-binding the type annotation (Int, g1) doesn't work. He needs to have the result-type for the application of "random"


Solution

  • Quick answer

    You can define the function you want as

    randomGens :: (RandomGen g) => g -> [g]
    randomGens g = let (g0,g1) = split g in g0 : randomGens g1
    

    Slightly longer answer

    The above probably isn't the best way to go about applying a function that requires randomness to a list. I might define a helper function to do that

    mapRandom :: (RandomGen g) => (g -> a -> b) -> g -> [a] -> (g, [b])
    mapRandom _ g []     = (g, [])
    mapRandom f g (a:as) = let (_,g1) = next g
                            in f g a : mapRandom f g1 as
    

    You can then write

    >> g <- newStdGen
    >> mapRandom f g [1..5]
    ([False,False,True,False,True], 1839473859 293842934)
    

    Best answer

    The function mapRandom looks very messy. That's because we have to mess around with the fiddly details of manually updating the generator. Fortunately, you don't have to do that! The package Control.Monad.Random gives you nice combinators to almost completely abstract away the idea of generators. Say you currently have

    f :: (RandomGen g) => g -> Int -> Bool
    f g n = let (x,_) = random g in x < n
    

    I would rewrite that to be

    f :: (RandomGen g) => Int -> Rand g Bool
    f n = do
      x <- getRandom
      return (x < n)
    

    and just use mapM to map this function over lists. You can run it with

    >> gen <- newStdGen
    >> runRand (mapM f [1..10]) gen
    ([False,True,True,False,True], 1838593757 1838473759)
    

    where the first element of the pair is the result of mapping your random function over the list, and the last element is the current value of the generator. Notice that when defining f you don't have to worry about the generator at all - Haskell takes care of updating the generator and generating new random numbers behind the scenes.