Random Intro

Include a StdGen in your model to let you randomize.

Source: LYaH, Input and Output.

Haskell functions are pure, which means the same input must give the same output. A “random number generator” that just puts out different numbers each time you call it is not pure. Instead, in Haskell, the randomization functions output a random value and a new random number generator that will give different results.

Quick Example

A random function in Haskell should put out a random number generator in addition to the number or numbers it makes.

import System.Random

rand1to6 :: StdGen -> (Int, StdGen)
rand1to6 g = randomR (1,6) g

main = do print "Setup"
          g <- newStdGen
          print "Random roll"
          let (n,gNew) = rand1to6 g
          print n

The example illustrates:

  1. Making a “standard generator” - the original source of randomness. NOTE: this requires the <- operator in main and cannot be done in elsewhere. (Slight lie.)
  2. The random function gives a new generator state gNew, which should be used for later random choices.

Getting Random Numbers

The most common method is randomR, which gives random numbers in a range.

  • (randomR) The randomR function gives a random number r in a range low <= r <= high. Notice that both ends of the range are included. Use a helper function with a type signature.

      rand20 :: StdGen -> (Int, StdGen)
      rand20 gen = randomR (1,20) gen
    
      main = do
                gen <- newStdGen
                let (n, gen') = rand20 gen
                print n
    

Multiple Random Numbers

This is tricker than I would like it to be. The most straightforward way is write a recursive function and manually manage the count.

kRolls :: Int -> StdGen -> [Int]
kRolls 0 _ = []
kRolls k g = n : krand (k-1) g'
  where (n,g') = randomR g (1,6)

If you want to make more random numbers after this, you need get the final state of the random number generator out of the function. That will be an exercise.

Exercises:

  1. Modify the code above to write a function that produces a list of integers and also a random generator that you can continue to use later.

     kRollsG :: Int -> StdGen -> ([Int], StdGen)`
    
  2. What is incorrect with the following code? If you can’t tell, print out the results and look for a pattern.

kRollOne :: StdGen -> Int
kRollOne g = n
  where (n,_) = randomR g (1,6)
kRollsBad :: Int -> StdGen -> [Int]
kRollsBad n g = [kRollOne g | _ <- [1..n]]

Making a StdGen

The best default choice is to use a newStdGen. You could also use mkStdGen if you want to specify the initial “seed” (so the random numbers will all follow the same pattern every time you run the program). Notice the use of let and <- in the examples.

  1. newStdGen: An IO function that uses the system’s random number state to create a random number generator that gives different results every time you run it.

  2. mkStdGen: Provide a seed number, like 109211, and get a random number generator that will produce the same results on every run. Good for debugging.

The code below creates two standard generators. This is just an example. Normally you would only use one.

main = do rng1 <- mkStdGen 109211
          rng2 <- newStdGen
          putStrLn "OK"

Common Problems

  • Copy and pasting examples can mess up indentation. All of the items in a do-block need to line up vertically
  • Variables: regular variables like let x = 5 use a different syntax from “IO variables” like y <- newStdGen.

Notes

  • Avoid getStdGen. Calling getStdGen repeatedly will give the exact same random number generator each time. (It does not “advance the state”.) If you ever have more than one standard generator at a time, this will not be what you want.
Last modified November 21, 2024: Change positions and title. (d4b42f5)