Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I use newStdGen or getStdGen instead of mkStdGen in haskell

Tags:

random

haskell

Trying to solve problem 23 of 99 Haskell Problems.

And I wrote this

rnd_select :: (Eq a) => [a] -> Int -> [a]
rnd_select [] _ = []
rnd_select _ 0 = []
rnd_select ys n = 
   let 
       (rnd_index, gen) = randomR (1, length ys) (mkStdGen 200)
       (x, xs) = removeAt rnd_index ys
   in x : rnd_select xs (n-1)

which works but I dont want use mkStdGen but use

  newStdGen or getStdGen

instead. I have seen the solutions to the problem but I want to understand how should I fix this code to do that and if its not possible why not because intuitively it feels like it should work but it doesn't.

like image 843
patternexon Avatar asked Apr 19 '12 01:04

patternexon


1 Answers

Remember that Haskell functions are pure; they must always return the same result given the same input. You could make your function return IO [a] instead, which would let you call newStdGen, but a better way is to keep your code pure by taking the random number generator as an additional argument to your function and also returning the new generator afterwards:

rnd_select :: (Eq a, RandomGen g) => [a] -> Int -> g -> ([a], g)
rnd_select [] _ gen = ([], gen)
rnd_select _ 0  gen = ([], gen)
rnd_select ys n gen = 
   let (rnd_index, gen') = randomR (1, length ys) gen
       (x, xs) = removeAt rnd_index ys
       (xs', gen'') = rnd_select xs (n-1) gen'
   in (x : xs', gen'')

Now you can use it with, e.g. getStdRandom :: (StdGen -> (a, StdGen)) -> IO a like this.

> getStdRandom (rnd_select [1..20] 10)
[12,11,14,4,16,7,1,2,18,15]

Passing the generators around manually can be somewhat tedious, though. One way of making this neater is to use the MonadRandom package.

rnd_select :: (MonadRandom m, Eq a) => [a] -> Int -> m [a]
rnd_select [] _ = return []
rnd_select _ 0  = return []
rnd_select ys n = do
  rnd_index <- getRandomR (1, length ys)
  let (x, xs) = removeAt rnd_index ys
  xs' <- rnd_select xs (n-1)
  return (x:xs')

Since IO is an instance of MonadRandom, you can use this directly as an IO action.

> rnd_select [1..20] 10
[20,18,12,13,5,7,17,9,3,4]
> rnd_select [1..20] 10
[9,18,4,20,6,5,3,15,13,7]

or you can use evalRand to run this in a pure monad, providing your own random number generator so you can get repeatable results (good for debugging / testing).

> evalRand (rnd_select [1..20] 10) (mkStdGen 200)
[4,16,15,13,8,20,6,14,5,3]
> evalRand (rnd_select [1..20] 10) (mkStdGen 200)
[4,16,15,13,8,20,6,14,5,3]
like image 168
hammar Avatar answered Sep 23 '22 00:09

hammar