2015年6月1日月曜日

HaskellのgetStdRandom関数

getStdRandomなにこれ?


手続き型言語の乱数生成ロジックになれた腐れ脳では、Haskellの乱数生成流儀についていくのがオックウになった今日この頃ではありますが、暇なのでSystem.Randomを弄って見ました。

乱数系の関数はSystem.Randomモジュールにあるのですがその中でgetStdRondom関数というのがあってこれがちょっと分かりにくかった。

その関数というのがこんな感じ、
Prelude> :m +System.Random
Prelude System.Random> :t getStdRandom
getStdRandom :: (StdGen -> (a, StdGen)) -> IO a
ちょっと使ってみる
Prelude System.Random> getStdRandom $ \g -> (random g, g)
(6296841432639150751,794839904 2103410263)
Prelude System.Random> getStdRandom $ \g -> (random g, g)
(6296841432639150751,794839904 2103410263)
Prelude System.Random>
結果は同じ、同じ乱数ジェネレータで乱数を生成しているため、同じ乱数しか返ってこない、これではこまる
getStdRandom関数は「StdGenを引数にとってタブル(値,乱数ジェネレータ)を返す関数」を引数にとるのですが、この時乱数ジェネレータを新しいものにするとそれを書き換えてくれるという便利な関数で、いたれりつくせりでちょっと分かりにくい。
で、どうなってるのか気になったのでソースコードをみてみた
...
theStdGen :: IORef StdGen
...
...
getStdRandom :: (StdGen -> (a,StdGen)) -> IO a
getStdRandom f = atomicModifyIORef theStdGen (swap . f)
  where swap (v,g) = (g,v)
theStdGenはIORefにくるまった乱数ジェネレータで、atomicModifyIORef関数は
Prelude System.Random> :m Data.IORef
Prelude Data.IORef> :t atomicModifyIORef
atomicModifyIORef :: IORef a -> (a -> (a, b)) -> IO b
Prelude Data.IORef> 
これは、「IORef aな変数とaをとって(更新後の値,戻値にしたい値)を返す関数」を引数にとるという頭が腐りそうになる関数らしく、IORefから乱数ジェネレータをはがして、それを第2引数の関数への入力へつかってその関数が返してきた、値と新しい乱数ジェネレータをもらって、値はそのまま返して、ついでに新乱数ジェネレータで旧乱数ジェネレータを更新する(であってるかどうか分かりませんが...)

長い...

第2引数のタプルは(値,乱数ジェネレータ)になっているのでswapしてる、なるほどぉー。
それが分かれば、こうやって使うのも自然に分かる
Prelude Data.IORef System.Random> :t randomR
randomR :: (Random a, RandomGen g) => (a, a) -> g -> (a, g)
Prelude Data.IORef System.Random> :t randomR (1, 100)
randomR (1, 100) :: (Random a, RandomGen g, Num a) => g -> (a, g)
Prelude Data.IORef System.Random> 
Prelude Data.IORef System.Random> getStdRandom $ randomR (1, 100)
95
この関数は範囲指定の乱数を生成してくれて、第1引数に範囲をしてすればgetStdRandomの引数に使える関数になる、ちなみにrandomRは新しい乱数ジェネレータを返してくれる。

リストで乱数が欲しいなら、こうして
Prelude> :m +System.Random
Prelude System.Random> :m +Control.Arrow
Prelude System.Random Control.Arrow> :t ((take 10) . fst . first randoms . split)
((take 10) . fst . first randoms . split)
  :: (Random a, RandomGen b) => b -> [a]
Prelude System.Random Control.Arrow> let gen = mkStdGen 10
Prelude System.Random Control.Arrow> :t ((take 10) . fst . first randoms . split) 
((take 10) . fst . first randoms . split)
  :: (Random a, RandomGen b) => b -> [a]
Prelude System.Random Control.Arrow> ((take 10) . fst . first randoms . split) gen
[429082465013243227,-2464015050900774578,7201080910402330150,-3257845164067810313,-4921980529382864518,1852978159347469817,8762143554925919814,-368579347710764073,-3030540427141715674,5472412\
645992684729]
Prelude System.Random Control.Arrow>
splitで乱数ジェネレータを更新しているので実行する度に違う乱数がちゃんと返ってくる、乱数生成にしても関数プログラミングでやるとまったく違ったアプローチになるのですね、やっぱ知っとかないとね。

とにかく、Haskellはたのしい。


0 件のコメント:

コメントを投稿