2

I want to generate N numbers using System.Random.next function. I implemented a function which takes a StdGen and a list of numbers and returns new generator and updated list of numbers:

import System.Random  

getRandNum :: StdGen -> [Int] -> (StdGen, [Int])
getRandNum gen nums = (newGen, newNums) 
                where
                (randNum, newGen) = next gen
                newNums = nums ++ [randNum]

Then I can use it in the following way:

λ: getRandNum (mkStdGen 1) []
(80028 40692,[39336])

But I have a problem with executing that function N times to get the list of random numbers. How can I chain it in a proper way?

I also tried the recursive way -- it works, but I'm sure this solution is far from elegant:

randomnumbers_ :: Int -> StdGen -> (StdGen, [Int])
randomnumbers_ 0 gen = (gen, [])
randomnumbers_ 1 gen = (newGen, [randNum])
  where
    (randNum, newGen) = next gen
randomnumbers_ n gen = (newGen2, nums ++ nums2)
  where
    (newGen, nums) = randomnumbers_ 1 gen
    (newGen2, nums2) = randomnumbers_ (n - 1) newGen

randomnumbers :: Int -> Int -> [Int]
randomnumbers n seed = snd $ randomnumbers_ n generator
  where
    generator = mkStdGen seed

By the way yes, I know that it can be implemented using the State monad and I know how to do it.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
trivelt
  • 1,913
  • 3
  • 22
  • 44
  • 1
    to answer the title of your question, we can compose binary functions which return tuples of same nature, by using `curry` and `uncurry` as needed. as you mention, that's what State Monad is doing. or by manually unpacking the tuples and using the appropriate values in chained -- or recursive -- function calls, as seen in the answers. – Will Ness Mar 19 '21 at 16:01

3 Answers3

3

The way to chain it is to implement it in such a way that you don't need to do it, i.e. so that it does the chaining by itself.

There's an inherent contradiction in your code anyway, you call it getRandNum, singular, and it indeed gets just one number, but the type is [Int].

So then we resolve all this, with a minimal edit to your code, as

getRandNums :: StdGen -> [Int]
getRandNums gen = randNum : newNums 
                where
                (randNum, newGen) = next gen
                newNums = getRandNums newGen 

This kind of scheme is typical for Haskell, building lists in the top-down fashion using what's known as guarded recursion, i.e. with recursion guarded by a lazy data constructor (in this case, :). Lists built with repeated appending of singletons as you do are very inefficient, have quadratic behavior when accessed.

Having newGen safely hidden inside, encapsulated, is an additional bonus. You don't want it exposed, what would be the use? Restarting the randoms generating sequence from the middle would just recreate the same sequence of numbers anyway.

And of course you can take as many of the numbers off that list as you wish, with take n.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
1

Your recursive solution is fine, and we can shorten it by removing the "1" case by inlining that where you are calling randomnumbers_ 1 gen.

randomnumbers_ :: Int -> StdGen -> (StdGen, [Int])
randomnumbers_ 0 gen = (gen, [])
randomnumbers_ n gen = (newGen2, num : nums)
   where
   (num, newGen) = next gen
   (newGen2, nums) = randomnumbers_ (n -1) newGen

We could improve this by swapping the pair: it's weird to have the standard next return the generator in second position but having randomnumbers_ return the generator in first position.

One could also remove the need of carrying around the generator state using a state monad, or some other random-related monad from the libraries (I'm not sure about what they recently added). If you work with a lot of random-generating functions, and carrying gen around is starting to get cumbersome, such a monad is probably the right tool.

chi
  • 111,837
  • 3
  • 133
  • 218
1

Non-monadic style:

Assuming you might require the final state of the generator for further processing, you can write your function like this, using manual generator state chaining:

import  System.Random

randomNumbers :: Int -> StdGen -> (StdGen, [Int])
randomNumbers count gen0 =
    if (count <= 0)
        then  (gen0, [])
        else  let  (v0, gen1) = next gen0
                   (gen2, vs) = randomNumbers (count-1) gen1
              in
                   (gen2, v0:vs)

Possible improvements:

  1. force a given output range, rather than taking the generator native one
  2. allow for any generator type, not just StdGen

That would give this similar code:

randomNumbersInRange :: RandomGen gt => (Int, Int) -> Int -> gt -> (gt, [Int])
randomNumbersInRange range count gen0 =
    if (count <= 0)
        then (gen0, [])
        else
            let (v0, rng1)   =  randomR range gen0
                (rng2, rest) =  randomNumbersInRange range (count-1) rng1
            in
                (rng2, v0 : rest)

Monadic style:

The monadic action object for N output values is very simple to write:

import  System.Random
import  Control.Monad.Random

mkRandSeqM :: (RandomGen tg, Random tv) => (tv,tv) -> Int -> Rand tg [tv]
mkRandSeqM range count = sequence (replicate count (getRandomR range))

and to use the action, you just need to feed it into library function runRand.

Testing under ghci:

 λ> 
 λ> action = mkRandSeqM (0,9) 20
 λ> 
 λ> :type (runRand action (mkStdGen 43))
(runRand action (mkStdGen 43))
  :: (Random tv, Num tv) => ([tv], StdGen)
 λ> 
 λ> runRand action (mkStdGen 43)
([3,3,7,8,1,9,1,1,5,3,1,2,6,7,4,1,7,8,1,6],1270926008 238604751)
 λ> 

Side note: Beware the Haskell System.Random package has recently gone thru large changes, resulting in the 1.2 version.

Philosophy of the changes here for example. Hopefully backward-compatible. You might want to check your distribution level.

jpmarinier
  • 4,427
  • 1
  • 10
  • 23
  • I have a strong aversion to writing any list-producing code that is doing any counting by itself. why should I know in advance how may items to produce? instead it is easy to amend your code (and mine) to producing `[(Double, StdGen)]` instead, and pass it through `map fst`, `take n`, `snd . last` and what have you. also, with State Monad we have all this already as `sequence`, `evalState` and `execState`. – Will Ness Mar 19 '21 at 15:50
  • @WillNess - maybe you don't need to “know” the exact number of items, just that it is to be much larger than (for example) 1,000. Say your Monte-Carlo simulation requires 100 millions thermal neutrons, hence one billion random Gaussian variates. It will run on 256 cores, hence 400,000 random neutrons per core. Once you start generating random neutrons, it makes sense to make and buffer a batch of (at least) 1,000 of them, among other things for the sake of instruction cache locality. At most you might let, on average, 5,000 Gaussian variates go to waste, the cost of which is negligible. – jpmarinier Mar 19 '21 at 19:13
  • interesting, thanks for the clarification! – Will Ness Mar 20 '21 at 06:38