参照透明性を破る最も分かりやすい物といえば乱数だが、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 を使うと良いみたい。