2 ms·
> the random number generator is stateful which conflicts with laziness It's not difficult to create a stateful computation in a non-strict runtime environment
by nybble41 6y ago
> the random number generator is stateful which conflicts with laziness
It's not difficult to create a stateful computation in a non-strict runtime environment. Laziness is compatible with (local) state. Haskell's random package is designed around explicitly threading the PRNG state through the computation:
import System.Random (Random, RandomGen, random)
import Data.List (unfoldr)
randomList ∷ (Random a, RandomGen g) ⇒ g → [a]
randomList = unfoldr (Just ∘ random)
Since the function passed to `unfoldr` always returns a `Just` value, the resulting lazy list never ends. If you're not familiar with `unfoldr`, it's essentially the opposite of `foldr`: instead of applying a reduction function to a list it produces a list by iteratively applying a generator function to a seed—in this case the PRNG state.
As it happens, this function already exists as `System.Random.randoms`. You use it like this:
main = do
(xs :: [Int]) ← randoms <$> newStdGen
mapM_ print xs
There are related functions `randomR` and `randomRs` which take a lower and upper bound in addition to the state.
If you were trying to achieve this effect with `randomIO` or `randomRIO`—perhaps to avoid explicitly threading the state—then you will run into difficulties. Not because of laziness; rather the contrary. The problem is that IO actions are strict by default, so you can't easily combine them to create a lazy list. You would need to use something like `System.IO.Unsafe.unsafeInterleaveIO` to defer the generation of the random values until they were actually demanded, but then you're mutating the shared, global PRNG state each time an item is evaluated from the list and the result would not be deterministic or repeatable even if you started with a known seed.