Search code examples
haskellrandomstate-monad

Generating new and different random lists in Haskell (without IO)?


This is very much a newbie question, and although I can find partial answers, I'm still having difficulty getting the whole thing to work. I have a collection of functions in a module with work with a particular data type I've designed. And sometimes I need to create a list of random values. We might consider my types as polynomials, given as lists of their coefficients. So I should just need to call a randomPoly function (say) to generate new lists each time it's called. For simplicity all lists can be the same length (say 4) and with the same sized elements - say between 0 and 9.

So what I want is to be able to do something like this (in ghci):

>>> setRandomSeed 42
>>> randomPoly
[6,5,0,4]
>>> randomPoly
[9,6,3,5]   
 >>> randomPoly
[7,3,9,2]

I can of course obtain different random lists by simply passing a new seed to the generator each time I need a new list:

>>> randomPoly st = map (`mod` 10) $ take 4 $ randoms (mkStdGen st) :: [Integer]

But I don't want to do this: I want to set an initial seed once, and let State take care of managing the current value of the generator from then on.

At this stage I'm not much interested in the nitty-gritty of monads, State, and all the rest - I'm looking for as close to an "off the shelf" solution as possible. I just want something I can use. Most examples seem to be very keen to teach all about how the State monad works - and this is a very honorable notion - but right now all I want is some quick and simple method of creating random lists when I want them.

For example here's a silly function which creates random lists until a sum of all values is even:

mkEvenPoly :: Int -> [Integer]
mkEvenPoly st
  | even $ sum p = p
  | otherwise    = mkEvenPoly $ s+1
  where
    p = map (`mod` 10) $ take 4 $ randoms (mkStdGen st) :: [Integer]

Note that each time I need to pass a new generating value to mkStdGen. Can I do this sort of thing with the State monad - storing the current value of the generator (which is returned by mkStdGen) and then using it for the next random call?


Solution

  • Here's an "off the shelf" solution that uses MonadRandom and comes pretty close to what (I think) you have in mind.

    I rewrote your randomPoly and mkEvenPoly functions to return a Rand monad. As such, they can be composed in a do statement into a composite Rand monad . Inside the do loop that generates this composite monad, random variables are set without the explicit use of a generator. A single generator is passed to runRand along with the composite monad to generate multiple random values.

    import Control.Monad.Random
    
    randomPoly :: Rand StdGen [Integer]
    randomPoly = map (`mod` 10) <$> take 4 <$> getRandoms
    
    mkEvenPoly :: Rand StdGen [Integer]
    mkEvenPoly = do
      p <- randomPoly
      let res
            | even $ sum p = return p
            | otherwise = mkEvenPoly
      res
    
    -- Use do notation to compose multiple monads into one.
    randomStuff :: Rand StdGen (Float, [[Integer]])
    randomStuff = do
      f <- getRandom
      p0 <- randomPoly
      p1 <- randomPoly
      e0 <- mkEvenPoly
      e1 <- mkEvenPoly
      e2 <- mkEvenPoly
      return (f, [p0, p1, e0, e1, e2])
    
    main = do
      -- set an initial seed once.
      g0 <- getStdGen 
    
      -- actually generate the random values.
      let (stuff, g1) = runRand randomStuff g0  
    
      print stuff