Skip to content

Instantly share code, notes, and snippets.

@thsutton
Created July 24, 2014 01:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thsutton/549ee9c57bc7ab5dff9e to your computer and use it in GitHub Desktop.
Save thsutton/549ee9c57bc7ab5dff9e to your computer and use it in GitHub Desktop.
Generate a stream of random numbers.
This is a Literate Haskell file: lines begining with `>` are Haskell
code and other lines are just plain old text (Markdown, actually). If
you save this file with a `.lhs` extension, the Haskell tools will all
run it just like a normal `.hs` file.
We're going to generate lists of random numbers and will need a few
helpful functions from the standard library:
> import Data.List (unfoldr)
> import System.Random
We'll use the `randomR` function to generate our random values. To make
it a little more convenient, we'll wrap it up in our own version which
has our own minimum and maximum values baked into it.
> -- | Use a random generator to generate one value.
> generateValue :: StdGen -> (Int, StdGen)
> generateValue gen = randomR (1,10) gen
This function takes a single argument -- a random number generator
state -- and produces a pair of values: a random number and a new
random number generator state.
If you think about it, you should be able to see why this must be the
case. A [pseudo] random number generator is just an algorithm which
uses a seed value to generate values which *look* random. Each time it
generates a new "random" value it also updates the seed value so that
the next number will be just as "random".
However, in the type of `generateValue` we've promised that it is a
pure function (i.e. it has no side-effects) so it can't possible
*update* the random number generator state we gave it. This leaves two
choices:
1. it can *ignore* the new state; or
2. it can *return* the new state along with the random number.
As we noted above, if you use the same generator state you'll generate
same "random" number, so we can rule out option one. That means it
*must* return both the random number and the new random number
generator state; any other option is silly.
Now that we can generate *one* value we can use this function in a
"loop" to generate a list of random values but we'll need to take care
to thread the random number generate state through each iteration of
the "loop". If we don't do this we'll wind up re-using the same random
number generator state value at every step and our list will just be
the same value repeated!
Rather than writing an imperative loop (which limits composition)
we'll make use of our recent reading in functional programming and use
an *unfold* or *anamorphism* (see: you read a paper and just a few
weeks later you're using it!). An unfold is a function which takes a
seed value and "unfolds" it into a list (or other structure). This is
the reverse of a fold which takes a list (or other structure) and
"folds" it up into a smaller value.
A few examples might make it a bit clearer. We can think of the `sum`
and `product` functions as being a fold of a list:
sum = foldl (+) 0
product = foldl (*) 1
(Indeed, according to the Haskell specification, that's the default
definition of these functions.)
Useful example unfolds are a little harder to come up with but many
functions with types like `a -> [a]` can be written as an unfold:
> -- | Generate a list of 'Int's from 1 to 'n'.
> upto :: Int -> [Int]
> upto n = unfoldr (work) 1
> where
> work m = if m > n
> then Nothing
> else Just (m, m+1)
This function is similar to the one we'll write to generate our list
of random values: a loop "body" function (`work`) takes an input seed
value and produces a value and a new seed. This worker function is
used together with an initial seed value (`1`) and the `unfoldr`
function to generate a list of the `Int` values from `1` to `n`.
Some lists (like the list of `Int`s from `1` up to `10`) are only
finite while others (like the list of even `Integer`s) are not. We'd
like to be able to generate both finite and infinite lists, so
`unfoldr` requires the worker function to return a `Maybe` instead of
a plain pair; `Nothing` means "the list is finished" and `Just (a, b)`
means "add `a` to the list, and use `b` in the next step".
So when we want to generate an infinite list, we just write a worker
function that always returns a `Just` value.
Now that we understand `unfoldr`, let's use it to generate an infinite
list of random numbers. We'll need to give it two arguments in our
function:
1. An initial random number generator state; and
2. A worker function with the type `StdGen -> Maybe (Int, StdGen)`.
We want our function to be as flexible as possible so we want to stay
away from the 'IO' monad, but the only way to *get* an `StdGen` value
is to do some I/O. Rather than mess around with `unsafePerformIO`
let's just take the initial state as an argument.
While we don't have a function with the right type to be the worker
function, we've got one that's pretty close: `generateValue` has the
type `StdGen -> (Int, StdGen)`. We want our list to be infinite, so we
want our worker function to always return a `Just` value. So our
worker function should call `generateValue` and then wrap the result
up in a `Just`. Hopefully you remember (or will remember, once you've
learned it) that data constructors like `Just` are functions and you
can use function composition with them, just like any other
function. So our worked function is just `Just . generateValue`!
> -- | Use a generator to produce an infinite list of random numbers.
> generateValues :: StdGen -> [Int]
> generateValues gen = unfoldr (Just . generateValue) gen
Notice that we're passing the random number generator state into this
function but -- unlike `generateValue` -- we are *not* returning the
"new" state for re-use. Why might this be?
Now, finally, we're ready to actually generate random values in our
program; we just have to create a new `StdGen` value and call the
`generateValues` function:
> main = do
> -- Create a new random number generator state. This bit needs to
> -- do I/O, so it has to happen in the IO monad at the top-level of
> -- our program.
> gen <- newStdGen
> -- Pass the generator into the generateValues function. This bit
> -- is pure because it *isn't* doing I/O, it's just using the value
> -- we I/Oed above.
> let vs = generateValues gen
> -- Now we can use our random values by, e.g., printing the first
> -- 10 items in the list.
> print $ take 10 vs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment