Created
July 24, 2014 01:03
-
-
Save thsutton/549ee9c57bc7ab5dff9e to your computer and use it in GitHub Desktop.
Generate a stream of random numbers.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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