4

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?

Alasdair
  • 1,300
  • 4
  • 16
  • 28
  • Possible duplicate of [State Monad, sequences of random numbers and monadic code](https://stackoverflow.com/questions/1956518/state-monad-sequences-of-random-numbers-and-monadic-code) – jberryman Jan 07 '18 at 22:18
  • You use the `random` library by passing around an `StdGen` as state (e.g. passing it into `random`, then proceeding with the `StdGen` returned from the call for your next pseudo-random value, etc). So you should call `mkStdGen` basically once. I would start by rewriting `mkEvenPoly` to be of type `StdGen -> [Integer]`, ignoring the `State` monad for now – jberryman Jan 07 '18 at 22:30
  • In terms of how to use `State` to abstract the "stateful" passing of `StdGen`, I think you should either start from one of the many small examples online, adapting it to your function incrementally, or just accept that you may need to invest some time to learn them. – jberryman Jan 07 '18 at 22:32
  • 1
    `MonadRandom` offers a fairly nice interface, and I think there may be other similar ones. – dfeuer Jan 07 '18 at 23:15
  • Thanks folks. Yes, of course - I am spending time to learn this - but it's taking quite a lot of time! And it's also somewhat frustrating: every time I think I have a concept licked, there's some small detail which requires another investment in digging into details. Ah well. No royal road, as they say... – Alasdair Jan 08 '18 at 02:48

1 Answers1

3

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
Dave Compton
  • 1,421
  • 1
  • 11
  • 18
  • This is great - thank you very much! Elegant code which not only answers my question, but also provides me with an example from which to learn. Such examples are not easy to find in the Haskell world, at least in package and module descriptions and code from hackage. Thank you again - this is more than I could have hoped for. – Alasdair Jan 08 '18 at 23:39