要するに:
evalRandIO $ evalStateT bar False
これを分けてみましょう。bar
を状態として維持し、Bool
ランダムな値を取得できる必要があります。どちらの方法でもネストできます。ここでは、状態をランダムの上に重ねることを選択しましたが、その逆も可能です。
evalStateT
この型シグネチャを持っています:
evalStateT :: Monad m => StateT s m a -> s -> m a
言い換えれば、他のモナドの上にある状態を必要とするものが与えられた場合、それはその状態部分を実装し、基礎となるモナドに関してそのアクションを提供します。名前のeval
一部は、結果の状態を破棄し、値だけを提供することを意味します。execStateT
結果の状態を提供し、出力をスローするものもあり、runStateT
両方のタプルを提供します。いずれにせよ、初期状態を与える必要があることに注意してください。あなたのコードは初期状態を使用していないので、 を使用することもできましたundefined
が、私はそれを使用False
しました。
状態ビットを実装したので、何が残るでしょうか?
ghci> :t evalStateT bar False
evalStateT bar False :: MonadRandom m => m Bool
ランダムな値を与えることができるモナドが必要です。さて、私たちはそれらの1つを持っています。Rand
やります。Rand
も、実際には状態モナドでもあるため、run
、eval
、およびバリアントを持ちます。カバーの下にexec
あるタイプのクラスの値を保持します。RandomGen
ここでは、最後に状態を破棄したくなく、結果も保持したいので、バリアントを使用します。さて、私たちは今何を持っていますか?run
ghci> :t runRand (evalStateT bar False)
runRand (evalStateT bar False) :: RandomGen g => g -> (Bool, g)
比較のために:
ghci> :t random
random :: (RandomGen g, Random a) => g -> (a, g)
これで、乱数ジェネレーターの状態を取得し、結果と新しい状態のペアを吐き出す単純な関数ができましrandom
たSystem.Random
。これをどのように使用できますか?さて、System.Random
モジュールを掘り下げると、getStdRandom
役に立ちそうです:
getStdRandom :: (StdGen -> (a, StdGen)) -> IO a
私たちが持っているような関数を取り、それをIO
アクションに変えます。(これは、 となるのが理にかなっていますIO
。グローバルな状態 (つまり、標準の乱数ジェネレーター) を取得し、それを更新しています。) それを追加した後、何が得られますか?
ghci> :t getStdRandom . runRand $ evalStateT bar False
getStdRandom . runRand $ evalStateT bar False :: IO Bool
まさに私たちが期待するものです: IO
a を生成するan Bool
. 数回実行します。
ghci> let barIO = getStdRandom . runRand $ evalStateT bar False
ghci> barIO
True
ghci> barIO
True
ghci> barIO
False
私にとっては十分にランダムです。ただし、Carsten がコメントで言及しているように、もっと短い方法があります。これは非常に一般的なユースケースであるため、Control.Monad.Random
モジュールはショートカットを提供します。evalRandIO
これは基本的にgetStdRandom . runRand
上記で使用したものです。最終的には簡単な交換です
evalRandIO $ evalStateT bar False