Как избежать IO
зависит от того, почему он вводится в первую очередь. Хотя генераторы псевдослучайных чисел по своей природе ориентированы на состояние, нет никакой причины, по которой IO
должен быть задействован.
Я собираюсь сделать предположение и сказать, что вы используете newStdGen
или getStdGen
, чтобы получить свой первоначальный PRNG. Если это так, то нет способа полностью сбежать IO
. Вместо этого вы можете начать сеанс PRNG напрямую с mkStdGen
, помня, что одно и то же начальное число приведет к той же последовательности «случайных» чисел.
Скорее всего, вы хотите получить PRNG внутри IO
, а затем передать его в качестве аргумента чистой функции. Конечно, в конце концов все будет заключено в IO
, но промежуточным вычислениям это не понадобится. Вот быстрый пример, чтобы дать вам идею:
import System.Random
type Rand a = StdGen -> (a, StdGen)
getPRNG = do
rng <- newStdGen
let x = usePRNG rng
print x
usePRNG :: StdGen -> [[Int]]
usePRNG rng = let (x, rng') = randomInts 5 rng
(y, _) = randomInts 10 rng'
in [x, y]
randomInts :: Int -> Rand [Int]
randomInts 0 rng = ([], rng)
randomInts n rng = let (x, rng') = next rng
(xs, rng'') = randomInts (n - 1) rng'
in (x:xs, rng'')
Вы можете заметить, что код, использующий PRNG, становится довольно уродливым из-за постоянной передачи текущего значения туда и обратно. Это также потенциально подвержено ошибкам, так как было бы легко случайно повторно использовать старое значение. Как упоминалось выше, использование одного и того же значения PRNG даст ту же последовательность чисел, что обычно не то, что вы хотите. Обе проблемы являются прекрасным примером того, где имеет смысл использовать State
монаду - которая выходит за рамки темы, но вы можете рассмотреть ее дальше.