あどけない話

インターネットに関する技術的な話など

はじめてのモナドの実装

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 の計算方法を工夫しないといけません。理解しやすいように、オリジナルと同じにすることに重点をおいたということで。