4

In a simulation problem, I need to generate thousands of agent objects behaving independantly of each other. For this I need to pass each of these agents a different random number generator. How to do this in Haskell? In a language like C, I can just generate a random number whenever it is needed, but in Haskell I need to do it only in the IO monad. The behaviour of the agents in entirely pure computation.

I am doing it in the following manner now:

import System.Random
main = do
   gen1 <- newStdGen
   let rands1 = randoms gen :: [Int]
   gen2 <- newStdGen
   let rands2 = randoms gen :: [Int]
   let sout = map (agentRun extraArgs) [rands1,rands2]
   writeFile "output" $ unlines sout

agentRun = <<some pure code that uses random numbers>>

One way I could solve this problem is given below, but it suffers from the fact that it requires to be run in IO monad only and I cannot just map the function on a list. Is there a better and more Haskell-like way for doing this? If yes, please give an sample code example.

Please note that the agents require access to infinite list as it is not known in advance how many random numbers are needed.

import System.Random
main = do
   dorun 10
dorun 0 = return ()
dorun n = do
   gen1 <- newStdGen
   let rands1 = randoms gen :: [Int]
   let sout = agentRun extraArgs rands1
   appendFile "output" sout
   dorun (n-1)

agentRun = <<some pure code that uses random numbers>>
mntk123
  • 905
  • 6
  • 18
  • 3
    Your way is perfectly correct - have your function take an infinite list as an argument and actually generate the list in `main`. Furthermore, an infinite list of `Int` and an infinite list of infinite lists of `Int` contain the same number of elements. You've demonstrated that you don't have an infinite number of agents, so why not have a finite number of random lists: `map (agentRun args . randoms) <$> replicateM numRuns newStdGen`. Finally, if you are dealing with randomness a lot, you may benefit from [some abstraction](https://hackage.haskell.org/package/MonadRandom-0.4.2.3). – user2407038 Oct 18 '16 at 19:02
  • 1
    `Data.List.unfoldr (Just . split) :: RandomGen b => b -> [b]` gives you an infinite list of generators, given one. If your function has a constraint like that, it can use infinite lists of infinite lists of `Int`s . I don't know if that's irrational somehow. – Michael Oct 18 '16 at 20:01
  • 1
    Note that `System.Random` is a very bad generator and might result in poor behavior of your program. Consider using one of the other generators, such as `System.Random.TF` from tf-random. – Thomas M. DuBuisson Oct 18 '16 at 20:14
  • You could make your functions work inside the `Rand` monad... – Bakuriu Oct 18 '16 at 21:03
  • @user2407038 I know that an infinite list of Int and an infinite list of infinite lists of Int contain the same number of elements. But the problem is I cannot easily (and efficiently) pass those to the different agents. – mntk123 Oct 19 '16 at 03:34

1 Answers1

2

Presumably you have some structure containing agent parameters, and a function

agentRun :: AgentParams -> [Int] -> Agent

Your problem is, how do you create an infinite list of Ints for each agent.

Presumably you start with a list of AgentParams, and you want to map agentRun over it, like this:

agentsParams :: [AgentParams]
agents = map (\p -> agentRun p ????) agentParams

The trouble is, you can't see how to get a different infinite list into each agent.

To do this you use the library functions unfoldr and split:

unfoldr :: (b -> Maybe (a, b)) -> b -> [a]
split :: (RandomGen g) => g -> (g,g)

unfoldr carries on applying its function until it returns Nothing.

split takes a random generator and returns two new ones. So Just . split makes a good argument for unfoldr.

infiniteGenerators :: (RandomGen g) => g -> [g]
infiniteGenerators = unfoldr (Just . split)

You can now map this infinite list of generators into an infinite list of infinite lists:

infiniteRandomLists :: (RandomGen g) => g -> [[Int]]
infininteRandomLists = map randoms . infiniteGenerators

Then you can use zipWith to get your list of agents:

agents gen = zipWith agentRun (infiniteRandomLists gen)

However when coding your agents you will likely find it easier to work with a generator rather than an infinite list of Ints. This is because of a common pattern in functions of this kind, where you write:

(gen2, gen3) = split gen1
result = foo gen2

This saves you having to pass the updated generator back out of foo. If you are taking numbers from an infinite list then you will have to do something like:

(randomList2, result) = foo randomList1

But a better solution would be to use some kind of random state monad to manage all this for you.

Paul Johnson
  • 17,438
  • 3
  • 42
  • 59
  • Great answer. Helped me learn an important thing about the generators. – mntk123 Oct 19 '16 at 05:34
  • Haskell continues to surprise me a lot - all pleasant surprises. This problem was beyond me and I thought, there is no better solution. But this little trick of `split`ing the generator took me off-guard. – mntk123 Oct 19 '16 at 06:09