ジェネレーターを抽象化するモナド内で乱数を必要とする計算を行うのが最もクリーンなことだと思います。そのコードは次のようになります。
StdGenインスタンスを状態モナドに配置し、状態モナドのgetおよびsetメソッドに砂糖を加えて、乱数を取得します。
まず、モジュールをロードします。
import Control.Monad.State (State, evalState, get, put)
import System.Random (StdGen, mkStdGen, random)
import Control.Applicative ((<$>))
(通常、インポートを指定しない可能性がありますが、これにより、各関数がどこから来ているのかを簡単に理解できます。)
次に、「乱数を必要とする計算」モナドを定義します。この場合、State StdGen
呼び出されたのエイリアスR
。(「ランダム」と「ランド」はすでに別の意味を持っているためです。)
type R a = State StdGen a
Rの仕組みは次のとおりです。乱数のストリーム(モナディック「アクション」)を必要とする計算を定義してから、次のように「実行」しrunRandom
ます。
runRandom :: R a -> Int -> a
runRandom action seed = evalState action $ mkStdGen seed
これはアクションとシードを取り、アクションの結果を返します。いつものようevalState
にrunReader
、など
今、私たちは州のモナドの周りに砂糖が必要です。get
StdGenを取得するために使用しput
、新しい状態をインストールするために使用します。つまり、1つの乱数を取得するには、次のように記述します。
rand :: R Double
rand = do
gen <- get
let (r, gen') = random gen
put gen'
return r
乱数ジェネレーターの現在の状態を取得し、それを使用して新しい乱数と新しいジェネレーターを取得し、乱数を保存し、新しいジェネレーターの状態をインストールして、乱数を返します。
これはrunRandomで実行できる「アクション」なので、試してみましょう。
ghci> runRandom rand 42
0.11040701265689151
it :: Double
これは純粋関数であるため、同じ引数を使用して再度実行すると、同じ結果が得られます。不純物は、runRandomに渡す「アクション」内にとどまります。
とにかく、関数は乱数のペアを必要としているので、乱数のペアを生成するアクションを記述しましょう。
randPair :: R (Double, Double)
randPair = do
x <- rand
y <- rand
return (x,y)
runRandomを使用してこれを実行すると、予想どおり、ペアに2つの異なる数値が表示されます。ただし、「rand」に引数を指定する必要はないことに注意してください。これは、関数が純粋であるためですが、「ランド」はアクションであり、純粋である必要はありません。
これで、関数を実装できますnormals
。 boxMuller
上で定義したとおり、型アノテーションを追加しただけなので、関数全体を読まなくても何が起こっているのかを理解できます。
boxMuller :: Double -> Double -> (Double, Double) -> Double
boxMuller mu sigma (r1,r2) = mu + sigma * sqrt (-2 * log r1) * cos (2 * pi * r2)
すべてのヘルパー関数/アクションが実装されたら、最終的にnormals
、正規分布のDoubleの(遅延生成された)無限リストを返す0個の引数のアクションを記述できます。
normals :: R [Double]
normals = mapM (\_ -> boxMuller 0 1 <$> randPair) $ repeat ()
必要に応じて、これを簡潔に書くこともできます。
oneNormal :: R Double
oneNormal = do
pair <- randPair
return $ boxMuller 0 1 pair
normals :: R [Double]
normals = mapM (\_ -> oneNormal) $ repeat ()
repeat ()
モナディックアクションに、関数を呼び出すための無限のストリームを提供します(これにより、法線の結果が無限に長くなります)。私は最初に書いていましたが、プログラムのテキストから[1..]
無意味なものを削除するために書き直しました。1
整数を操作しているのではなく、無限のリストが必要なだけです。
とにかく、それだけです。これを実際のプログラムで使用するには、Rアクション内でランダムな法線を必要とする作業を行うだけです。
someNormals :: Int -> R [Double]
someNormals x = liftM (take x) normals
myAlgorithm :: R [Bool]
myAlgorithm = do
xs <- someNormals 10
ys <- someNormals 10
let xys = zip xs ys
return $ uncurry (<) <$> xys
runRandom myAlgorithm 42
モナディックアクションをプログラミングするための通常の手法が適用されます。アプリケーションをできるだけ少なくしてくださいR
。そうすれば、物事が煩雑になることはありません。
ああ、そして他のことについて:怠惰はモナド境界の外にきれいに「漏れる」可能性があります。これは、次のように書くことができることを意味します。
take 10 $ runRandom normals 42
そしてそれは動作します。