回避する方法IO
は、そもそもなぜ導入されているのかによって異なります。疑似乱数ジェネレーターは本質的に状態指向ですが、IO
関与する必要はありません。
推測して、最初のPRNGを使用しているnewStdGen
か取得していると言います。getStdGen
その場合、完全に脱出する方法はありません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
は話題から外れていますが、次に調べてみることをお勧めします。