How to use random numbers?
Albert Y. C. Lai, trebla [at] vex [dot] netRandom numbers (of course I mean pseudo random numbers) come from
generators with implicit or explicit state. This means the use of
random numbers in Haskell (through the System.Random
library)
takes a bit of getting used to, as it involves state passing. The
problem is standard and the solutions are also standard.
Most of you who need help with random numbers come from an
imperative background, so I start with an imperative way with the
IO
monad. Afterwards I will also add functional ways, and a
way with a custom state monad.
Task
Throughout all the methods, I exemplify with the task of generating a random list of this form: its length is 1 to 7 inclusive (determined uniformly randomly), and each item in the list is aFloat
between 0.0 and 1.0.
Imperatively
There is a global generator in the IO
monad. You can
initialize it and get random numbers from it. Here are some common
functions you can use:
setStdGen :: StdGen -> IO ()
-
Initializes, or sets, the global generator to the given one. Usually you give it one created from a given seed, i.e., using
mkStdGen
. So, a silly usage looks like:setStdGen (mkStdGen 42)
Of course you replace
42
by anInt
acquired from input, the command line, ...You have the choice of calling
setStdGen
or not. If you don't, the global generator is still usable, since the runtime initializes it with an arbitrary seed at startup, and every time it is a different seed. randomRIO :: (Random a) => (a,a) -> IO a
-
Returns a random number of type
a
in the given range. The global generator is also updated. You specify the range (inclusive) by the tuple parameter. This example gets a random letter between "a" and "z" inclusive:c <- randomRIO ('a','z')
Can
a
be any type you like? Not really. In Haskell 98 theRandom
library supportsBool
,Char
,Int
,Integer
,Float
, andDouble
. (The support is extensible - you can write or obtain code to support more types - but that's another story...) In general types of classRandom
can be used. randomIO :: (Random a) => IO a
-
Returns a random number of type
a
. (Cana
be any type you like? See above.) The global generator is also updated. This example gets a randomDouble
:x <- randomIO :: IO Double
The range of the returned random number varies by the type, and by Murphy's Law is invariably different from what you expect. Don't assume; check the docs or ask around to find out. Or just use
randomRIO
.
Note that these are IO
functions, so you can use them
in your own IO
functions only; equivalently, if you write a
function to use them, its type becomes an IO
function
too. So for example, the above example snippets are intended to be in
a do
-block for IO
. This is not a problem,
just a reminder, because we plan to be imperative.
Here is how to perform the example task in the IO
monad.
import Random main = do { setStdGen (mkStdGen 42) -- optional ; s <- randomStuff ; print s } randomStuff :: IO [Float] randomStuff = do { n <- randomRIO (1,7) ; sequence (replicate n (randomRIO (0,1))) }
Pure-Functionally
There are several reasons why you may like to know how to use
random numbers functionally: you have curiosity; you want to escape
from the IO
monad; for concurrency or other reasons you
want several generators in co-existence, and a shared global one is
problematic.
There are two ways to use random numbers functionally. One is to work off a stream (infinite list) of random numbers. Another is to pass around generators as part of function arguments and return values.
Pure-Functionally: Stream of Random Numbers
Here are some common functions for creating a generator and infinite lists of random numbers:
mkStdGen :: Int -> StdGen
Makes a generator from the given seed.
randomRs :: (Random a, RandomGen g) => (a, a) -> g -> [a]
Infinite list of random numbers from the given generator in the given range. Example: letters between "a" and "z" inclusive, from seed 42:
randomRs ('a', 'z') (mkStdGen 42)
The typea
is the type of the random numbers. The typeg
looks general, but in practice is alwaysStdGen
.randoms :: (Random a, RandomGen g) => g -> [a]
Infinite list of random numbers from the given generator. Example:
Double
s from seed 42:randoms (mkStdGen 42) :: [Double]
The range is determined by the type, and you should always check the docs to find out what it is. Or just userandomRs
.
Note that these are functional - there is no in-place, destructive
update. In particular the generator is not updated. If you use a
generator to make a first list, then use the same generator to make a
second list...
g = mkStdGen 42
a = randoms g :: [Double]
b = randoms g :: [Double]
guess what, referential transparency dictates that the two lists are
the same! (If you want two different lists, but you only have one
seed, I will show you a way soon.)
Here is one way to do the task of creating a random list of 1 to 7
Float
s.
import Random main = do { let g = mkStdGen 42 ; let [s] = take 1 (randomStuff g) ; print s } randomStuff :: RandomGen g => g -> [[Float]] randomStuff g = work (randomRs (0.0, 1.0) g) work :: [Float] -> [[Float]] work (r:rs) = let n = truncate (r * 7.0) + 1 (xs,ys) = splitAt n rs in xs : work ys
Apart from the necessary I/O of printing the answer, it is purely functional. From the generator, it produces an infinite list of random numbers, and then from that it produces an infinite list of answers. (And then the consumer just takes one.) I do this because even though our task today asks for just one, in reality you need many and I hope this code sets an example.
Here is how the code works. From one generator, it creates an
infinite list of random Float
s of the right range. One
number is snatched off and scaled up to determine the random length 1
to 7, then that many is snatched off from the remaining to yield an
answer, and this procedure is repeated over the yet remaining for more
answers. In other words, the input list is broken into one number
r
and the rest rs
, r
determines
the desired length n
, and rs
is further
split into xs
, the answer of length n
, and
ys
, the yet unused part. Then ys
is used
for more answers.
Now here is a variation. I have used the same random stream for both the length and the content. I have found a way to do it, but in general the same trick may be inapplicable to other tasks. You may like to use two separate streams. Here is how. First, I introduce the function that creates two generators from one:
split :: (RandomGen g) => g -> (g, g)
Creates two different generators from one source. It is not advisable to re-use the original source for other purposes. Example:
g = mkStdGen 42 (ga, gb) = split g -- do not use g elsewhere
If you want more than two, you can pick one of the new two andsplit
again. Example:g = mkStdGen 42 (ga, g') = split g (gb, gc) = split g' -- do not use g, g' elsewhere
We can apply split
to obtain two generators, from
which we can produce two random streams. Here is the idea applied to
our task:
randomStuff :: RandomGen g => g -> [[Float]] randomStuff g = work (randomRs (1, 7) ga) (randomRs (0.0, 1.0) gb) where (ga,gb) = split g work :: [Int] -> [Float] -> [[Float]] work (n:ns) rs = let (xs,ys) = splitAt n rs in xs : work ns ys
It splits the generator into two, and produces two streams accordingly. Since the two streams are created to be of just the right types and ranges, their use in the code is much more apparent. This code is more generalizable.
I have hardcoded the seed in the main program. Normally you would
want to obtain the seed elsewhere - from some input, from a file, from
the clock, from some device - these are all very do-able in the main
program, since it runs in the IO
monad and has access to
all those. You can also fetch the global generator and pass it
along:
main = do { g <- getStdGen ; let [s] = take 1 (randomStuff g) ; print s }
state monad
To do.
I have more Haskell Notes and Examples