Tengu
Tengu

Reputation: 1034

Trying to figure out `random` function in Haskell

I just learned about random function.

To my understanding, random function takes a value of type which is an instance of RandomGen and returns a random value whose value we can specify. On the other hand, mksdGen takes an Int and generates a random generator which random function needs.

I was trying to generate random Bool values. In order to do that, I made a function randomBool.

randomBool :: Int -> Bool
randomBool = fst . random . mkStdGen

Then I found a lot more Trues than Falses with small numbers. And I was curious about it and checked as following

> length $ takeWhile randomBool [1..]
53667

I think this means that for the first 53667 positive integers, random . mkStdGen returns True, which do not seem to be very random to me. Is it very normal? Or am I doing something wrong that make True happen more easily?

Upvotes: 12

Views: 2920

Answers (3)

Travis Athougies
Travis Athougies

Reputation: 420

Computers are deterministic and can't generate random numbers. Rather, they rely on mathematical formulas that return a distribution of numbers that look random. These are called pseudo-random number generators. However, because of the determinism, we have the problem that if we ran these formulas the same way during each invocation of our program, we would get the same random number generators. Obviously, this is no good, since we want our numbers to be random! Thus, we have to provide the random generator an initial seed value that changes from run-to-run. For most people (i.e., those not doing cryptographical stuff), the random number generator is seeded by the current time. In Haskell, this pseudo-random generator is represented by the StdGen type. The mkStdGen function is used to create a random number generator with a seed. Unlike C, where there is one global random number generator, in Haskell, you can have as many as you like, and you can create them with different seeds.

However, there is a caveat: since the numbers are pseudo-random, there is no guarantee that random number generators created with different seeds return numbers that look random compared to the other. This means that when you call randomBool and give it successive seed values, there is no guarantee that the number you get from the StdGen you create is random compared to the StdGen seeded with its successor. This is why you get almost 50000 True's.

In order to get data that actually looks random, you need to continue using the same random number generator. If you notice, the random Haskell function has a type StdGen -> (a, StdGen). Because Haskell is pure, the random function takes a random number generator, generates a pseudo-random value (the first element of the return value) and then returns a new StdGen which represents the generator seeded with the original seed, but ready to give a new random number. You need to keep this other StdGen around and pass it to the next random function in order to get random data.

Here is an example, generating three random bools, a, b, and c.

randomBools :: StdGen -> (Bool, Bool, Bool)
randomBools gen = let (a, gen') = random gen
                      (b, gen'') = random gen''
                      (c, gen''') = random gen'''
                   in (a, b, c)

Notice how the gen variable is "threaded" through the calls to random.

You can simplify passing state by using a state monad. For example,

import Control.Monad.State
import System.Random

type MyRandomMonad a = State StdGen a

myRandom :: Random a => MyRandomMonad a
myRandom = do
  gen <- get -- Get the StdGen state from the monad
  let (nextValue, gen') = random gen -- Generate the number, and keep the new StdGen
  put gen' -- Update the StdGen in the monad so subsequent calls generate new random numbers
  return nextValue

Now you can write the randomBools function as:

randomBools' :: StdGen -> (Bool, Bool, Bool)
randomBools' gen = fst $ runState doGenerate gen
  where doGenerate = do
          a <- myRandom
          b <- myRandom
          c <- myRandom
          return (a, b, c)

If you want to generate a (finite) list of Bools, you can do

randomBoolList :: StdGen -> Int -> ([Bool], StdGen)
randomBoolList gen length = runState (replicateM length myRandom) gen

Notice how we return the StdGen as the second element of the returned pair, to allow it to be given to new functions.

More simply, if you just want to generate an infinite list of random values of the same type from an StdGen, you can use the randoms function. This has the signature (RandomGen g, Random a) => g -> [a]. To generate an infinite list of Bool using a starting seed of x, you simply run randoms (mkStdGen x). You can implement your example using length $ takeWhile id (randoms (mkStdGen x)). You should verify that you get different values for different initial values of x, but always the same value if you supply the same x.

Finally, if you don't care about being tied to the IO monad, Haskell also provides a global random number generator, much like imperative languages. Calling the function randomIO in the IO monad will give you a random value of whatever type you like (as long as it is an instance of the Random typeclass, at least). You can use this similarly to myRandom above, except in the IO monad. This has the added convenience that it is pre-seeded by the Haskell runtime, meaning you don't have to even worry about creating an StdGen. So, to create a random list of 10 Bools in the IO monad, all you have to do is replicateM 10 randomIO :: IO [Bool].

Hope this helps :)

Upvotes: 3

jtobin
jtobin

Reputation: 3273

Informally, when you call mkStdGen with seeds that are close together you will get two 'similar' generators. In your example you're actually creating new generators for each seed supplied, and since those seeds are 1, 2, 3, etc., they'll yield similar streams.

When you call random with a generator, you actually get back a new generator in the second element of the pair:

Prelude System.Random> random (mkStdGen 100) :: (Bool, StdGen)
(True,4041414 40692)

So a good idea is to use this provided generator for your next call to random. I.e.,

Prelude System.Random> let (i, gen0) = random (mkStdGen 100) :: (Bool, StdGen)
Prelude System.Random> let (j, gen1) = random gen0           :: (Bool, StdGen)
Prelude System.Random> let (k, gen2) = random gen1           :: (Bool, StdGen)
Prelude System.Random> (i, j, k)
(True, False, False)

So to make a bunch of random values, you want to pass the generator as state. You can set this up manually via a State monad or something, or just use the randoms function, which handles passing the generator state for you:

Prelude System.Random> take 10 $ randoms (mkStdGen 100) :: [Bool]
[True,False,False,False,False,True,True,False,False,True]

If you don't particularly care about being in IO (it happens) you can use randomIO:

Prelude System.Random> import Control.Monad
Prelude System.Random Control.Monad> replicateM 10 randomIO :: IO [Bool]
[True,True,False,True,True,False,False,False,True,True]

This section of LYAH might be a useful read.

Upvotes: 16

snak
snak

Reputation: 6703

A random generator created by mkStdGen doesn't necessarily generate a random value as its first result. To generate the next random number, use the random generator returned by the previous random call.

For example, this code generates 10 Bools.

take 10 $ unfoldr (Just . random) (mkStdGen 1) :: [Bool]

Upvotes: 2

Related Questions