5

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.

patternexon
  • 63
  • 2
  • 5
  • possible duplicate of [how to make Random in haskell in pure function](http://stackoverflow.com/questions/2926267/how-to-make-random-in-haskell-in-pure-function) – Daniel Wagner Apr 19 '12 at 02:08
  • See also [this slightly more inflamed question](http://stackoverflow.com/questions/7329361/haskell-random-generator-how-to-make-easier-to-use). These are the top two results on Google for "how to do randomness in Haskell". – Daniel Wagner Apr 19 '12 at 02:09

1 Answers1

9

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]
Janus Troelsen
  • 20,267
  • 14
  • 135
  • 196
hammar
  • 138,522
  • 17
  • 304
  • 385
  • 1
    `newStdGen` modifies `getStdGen` in place. Doesn’t that break referential transparency? https://hackage.haskell.org/package/random-1.1/docs/src/System-Random.html#newStdGen – Reb.Cabin Dec 24 '17 at 23:13