0

When running genRandTupe I continuously get the same random numbers, however when running genrandList with gen as the argument i get a new set of numbers every time. How would I solve this? Why doesn't g = newStdGen generate a new random number?

import System.Random
import System.IO.Unsafe

type RandTupe = (Int,Int)
genRandTupe :: IO RandTupe
genRandTupe = let [a,b] = genrandList g in return (a,b) where g = newStdGen
genrandList gen = let g = unsafePerformIO gen in take 2 (randomRs (1, 20) g)
gen = newStdGen
2
  • If you actually want a different random number each time, you need to either call randomRIO and put IO in the type signature, or call randomR, take a seed as input, and return a new seed as output. I'm sure somebody else has already asked about this somewhere on SO. Commented Feb 18, 2016 at 16:00
  • I'll add my voice to what hopefully should become a choir: don't use unsafePerformIO! (Unless you really, really, really know what you're doing.) Commented Feb 18, 2016 at 22:41

1 Answer 1

5

genRandTupe is a constant applicative form. That means any local variables in its let or where blocks are memoised. Usually quite a convenient feat!

In your case, it means that the list [a,b] is only computed once in your whole program. The way it is computed is actually illegal (don't use unsafePerformIO!), but it doesn't really matter because it only happens once anyway. Wrapping this constant tuple then into IO with return is actually completely superfluent, you could as well have written

genRandTupe' :: RandTupe
genRandTupe' = let [a,b] = genrandList g in (a,b)
 where g = newStdGen

OTOH, when you evaluate genrandList gen (not a CAF) in multiple separate places, the result will not necessarily be stored. Instead, that function is calculated anew, using unsafePerformIO to unsafely modify the global state (or maybe not... the compiler is actually free to optimise this away since, you know, genRandList is supposedly a pure function...) and therefore yield a different result each time.

The correct thing, of course, is to stay the heck away from unsafePerformIO. There's actually no need at all to do IO in genRandList, since it already accepts a random generator... just bind that generator out from IO before passing it:

genRandTupe'' :: IO RandTupe
genRandTupe'' = do
    g <- newStdGen
    let [a,b] = genrandList g
    return (a,b)

randListFrom :: RandomGen g => g -> [Int]
randListFrom g = take 2 (randomRs (1, 20) g)

Note that, because the let [a,b] = ... now is in a do block, it's now in the IO monad, decoupled from the CAF closure of genRandTupe''.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks, definitely helped.
Sorry about the alarmist tone, but unsafePerformIO is a pest. (And, it really doesn't belong to Haskell at all, just to the Foreign Function Interface.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.