Learn You a Haskell for Great Good! - Miran Lipovaca [86]
We could make a function that generates a finite stream of numbers and a new generator like this:
finiteRandoms :: (RandomGen g, Random a, Num n) => n -> g -> ([a], g)
finiteRandoms 0 gen = ([], gen)
finiteRandoms n gen =
let (value, newGen) = random gen
(restOfList, finalGen) = finiteRandoms (n-1) newGen
in (value:restOfList, finalGen)
Again, this is a recursive definition. We say that if we want zero numbers, we just return an empty list and the generator that was given to us. For any other number of random values, we first get one random number and a new generator. That will be the head. Then we say that the tail will be n - 1 numbers generated with the new generator. Then we return the head and the rest of the list joined and the final generator that we got from getting the n - 1 random numbers.
What if we want a random value in some sort of range? All the random integers so far were outrageously big or small. What if we want to throw a die? Well, we use randomR for that purpose. It has this type:
randomR :: (RandomGen g, Random a) :: (a, a) -> g -> (a, g)
This means that it’s kind of like random, but it takes as its first parameter a pair of values that set the lower and upper bounds, and the final value produced will be within those bounds.
ghci> randomR (1,6) (mkStdGen 359353)
(6,1494289578 40692)
ghci> randomR (1,6) (mkStdGen 35935335)
(3,1250031057 40692)
There’s also randomRs, which produces a stream of random values within our defined ranges. Check this out:
ghci> take 10 $ randomRs ('a','z') (mkStdGen 3) :: [Char]
"ndkxbvmomg"
It looks like a super secret password, doesn’t it?
Randomness and I/O
You may be wondering what this section has to do with I/O. We haven’t done anything concerning I/O so far. We’ve always made our random number generator manually by creating it with some arbitrary integer. The problem is that if we do that in our real programs, they will always return the same random numbers, which is no good for us. That’s why System.Random offers the getStdGen I/O action, which has a type of IO StdGen. It asks the system for some initial data and uses it to jump-start the global generator. getStdGen fetches that global random generator when you bind it to something.
Here’s a simple program that generates a random string:
import System.Random
main = do
gen <- getStdGen
putStrLn $ take 20 (randomRs ('a','z') gen)
Now let’s test it:
$ ./random_string
pybphhzzhuepknbykxhe
$ ./random_string
eiqgcxykivpudlsvvjpg
$ ./random_string
nzdceoconysdgcyqjruo
$ ./random_string
bakzhnnuzrkgvesqplrx
But you need to be careful. Just performing getStdGen twice will ask the system for the same global generator twice. Suppose we do this:
import System.Random
main = do
gen <- getStdGen
putStrLn $ take 20 (randomRs ('a','z') gen)
gen2 <- getStdGen
putStr $ take 20 (randomRs ('a','z') gen2)
We will get the same string printed out twice!
The best way to get two different strings is to use the newStdGen action, which splits our current random generator into two generators. It updates the global random generator with one of them and yields the other as its result.
import System.Random
main = do
gen <- getStdGen
putStrLn $ take 20 (randomRs ('a','z') gen)
gen' <- newStdGen
putStr $ take 20 (randomRs ('a','z') gen')
Not only do we get a new random generator when we bind newStdGen to something, but the global one gets updated as well. This means that if we do getStdGen again and bind it to something, we’ll get a generator that’s not the same as gen.
Here’s a little program that will make the user guess which number it’s thinking of:
import System.Random
import Control.Monad(when)
main = do
gen <- getStdGen
askForNumber gen
askForNumber :: StdGen -> IO ()
askForNumber gen = do
let (randNumber, newGen)