言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

Haskell の乱数に関する実験

参照透明性を破る最も分かりやすい物といえば乱数だが、Haskell の乱数ライ
ブラリは結構面白いので詳しく見てみた。

> import System.Random -- ライブラリを呼ぶ丁寧なやり方。

単純な呼び出し方はこのとおり、ライブラリの中(Windows の hugs だと 
c:/Program Files/Hugs98/libraries/System/Random.hs に書いてある。

> -- 実行の仕方 :  rollDice >>= print
> rollDice :: IO Int
> rollDice = getStdRandom (randomR (1,6))

hugs で実行するためには rollDice >>= print のようにする。この記法の正
確な所は私も理解していないが、rollDice は IO Int を返すのでこれを実行
して値を取り出すために >>= print が使える。

問題は getStdRandom の型 getStdRandom :: (StdGen -> (a,StdGen)) -> IO
a なんの事やらさっぱりわからん。字面をそのまま読むと、引数としては乱数
発生器を受け取り、オブジェクトと乱数発生器を返す関数が引数として必要な
ようだ。ここで Haskell の世界観がひとつ垣間見える。Haskell は世界の状
態を変える事は出来ない。乱数発生器があったとしても、Haskell の世界では
毎回同じ値が出てしまうだろう。そこで、値を取り出す度に、新しく乱数発生
器を作り出している。たぶん。

一方 randomR の型は (Random a, RandomGen b) => (a,a) -> b -> (a,b) で
ある。=> の前はクラスを表す。クラスと言っても、他の言語で言うインタフェー
スの事だ。Random は乱数の対象になるクラスを表し、標準で Int、Char、
Bool がある。randomR は範囲と乱数発生器を受け取り、結果を返す関数とい
う事になる。(randomR (1,6)) の所で引数として範囲しかないが、乱数発生器
は getStdRandom の方で与えるから良いのだ。

では肝心の乱数発生器はどこで作ってるかと言うと、getStdRandom の定義に
ある getStdGen で取り出す。必要以上にややこしい気もするが、ようするに、

- getStdRandom は、標準の場所から乱数を取り出す事を示す。
- randomR は結果の型と範囲を示す。

がんばって調べたのは、他の乱数の取り出し方を知りたかったからだ。例えば 
haskell は無限リストが扱えるので、ガバッと乱数の無限列を作りたい。ライ
ブラリの中には randoms :: RandomGen g => g -> [a] というのがある

> -- 実行の仕方: randomList >>= print . take 5
> randomList :: IO [Integer]
> randomList = do gen <- getStdGen
>                 return (randoms gen)

まず関数の型を決める。整数の列は [Integer] だが、実行の度に違う物が欲
しい気持ちをこめて IO [Integer] と書く。getStdGen で求める予定の乱数発
生器はそのままライブラリの random に与えるつもりになり、返したい気持ち
を込めて return に渡す。このような IO 型の関数は、評価の段階では気持ち
を込められているだけで実際には何もしない。こういうのを IO モナドという
らしい。

hugs は式を評価した結果が IO モナドなのを知ると実行するわけだが、実行
出来るのは IO ()、すなわち返り値の無い IO モナドだけだ、返り値の欲しい
時は自分で工夫する。>>= はモナドを実行して結果を右辺の関数に渡すので、
print は最後にそれを表示する。無限列が全部表示されては大変なので、take 
で先頭のいくつかだけ表示するようにすると良い。

この関数を実行すると、毎回同じ列が出てきてしまうが、これで困る場合は 
getStdGen の代わりに newStdGen を使うと良いみたい。