Haskell/Understanding monadsのモナドの説明は、今まで見た中で一番分りやすいです。
しかし、サイコロのコードは、疑似モナドであって、モナドではないので、動きません。return や >>= が使えないからです。
というわけで、疑似モナドのサイコロのコードを動くようにしてみました。
type Seed = Int randomNext:: Seed -> Seed randomNext rand = if newRand > 0 then newRand else newRand + 2147483647 where newRand = 16807 * lo - 2836 * hi (hi,lo) = rand `divMod` 127773 ---------------------------------------------------------------- type Random a = Seed -> (a, Seed) rollDie :: Random Int rollDie = \seed -> ((seed `mod` 6) + 1, randomNext seed) ---------------------------------------------------------------- (>=>) :: Random a -> (a -> Random b) -> Random b m >=> g = \seed0 -> let (result1, seed1) = m seed0 (result2, seed2) = (g result1) seed1 in (result2, seed2) rtrn :: a -> Random a rtrn x = \seed0 -> (x, seed0) ---------------------------------------------------------------- sumTwoDice :: Random Int sumTwoDice = rollDie >=> (\die1 -> rollDie >=> (\die2 -> rtrn (die1 + die2)))
return は rtrn、>>= は >=> としています。こんな感じに動きます。
sumTwoDice 100 → (10,330237489)
でも、Random をモナドにしないと、やっぱり気持ち悪いままです。実装方法が全然分らなかったのですが、Monadic Programming in Schemeを読んでようやく理解できました。
僕のはじめてのモナドは、このようになりました。
type Seed = Int randomNext:: Seed -> Seed randomNext rand = if newRand > 0 then newRand else newRand + 2147483647 where newRand = 16807 * lo - 2836 * hi (hi,lo) = rand `divMod` 127773 ---------------------------------------------------------------- data Random a = Random (Seed -> (a, Seed)) rollDie :: Random Int rollDie = Random $ \seed -> ((seed `mod` 6) + 1, randomNext seed) ---------------------------------------------------------------- instance Monad Random where Random m >>= g = Random $ \seed0 -> let (result1, seed1) = m seed0 Random m' = g result1 (result2, seed2) = m' seed1 in (result2, seed2) return x = Random $ \seed0 -> (x, seed0) ---------------------------------------------------------------- sumTwoDice :: Random Int sumTwoDice = do die1 <- rollDie die2 <- rollDie return (die1 + die2) ---------------------------------------------------------------- run (Random dice) seed = dice seed
do が使えています。\^^/
もちろん動きますよ。
run sumTwoDice 100 → (10,330237489)
これで、かなりモナドが理解できた気がします。。。
追記
今のコードでは、2つのサイコロの目は同じになるので、randomNext か rollDie の計算方法を工夫しないといけません。理解しやすいように、オリジナルと同じにすることに重点をおいたということで。