今日はモナド実装にチャレンジ。
モナドの機能は、contextualな値に関数を適用すること。
functorやapplicativeも似たようなことを行うが、モナドのいいところは演算子のつなぎ目をきれいに書けるところ。
とりあえず、書いてみる。まずモナドクラスのインスタンスにする型を定義する。
Maybe型を応用して、即成功、即失敗、判断を後回しにする、という3つのcontextを持つ型を定義する。
data Check a = YES | NO | Unknown a deriving (Show, Eq)
この型を利用するために以下の関数を定義する。
passTest f x = if f x == True then YES else Unknown x
failTest f x = if f x == True then NO else Unknown x
determine f x = if f x == True then YES else NO
さらに、この型をモナドインスタンスとして実装する。
instance Monad Check where
return x = Unknown x
YES >>= _ = YES
NO >>= _ = NO
Unknown x >>= f = f x
これを使うと、ややこしい制御フローがきれいに書ける。例えば、うるう年判定をするプログラム。やたらとif-elseが多い。
isLeapYear x = if x `mod` 400 == 0
then True
else if x `mod` 100 == 0
then False
else if x `mod` 4 == 0
then True
else False
モナドを使うときれいに書ける。
divisable y x = x `mod` y == 0
isLeapYear x = return x >>= passTest (divisable 400)
>>= failTest (divisable 100)
>>= determine (divisable 4)
モナドは、制御フローを表に出さずに演算を繋げることができるデザインパターンと考えることもできる。見てのとおり制御フローをcomposableな要素として部品化することができ、if-else-break文のボイラープレート除去を行うことができる。条件が増えたら、新しいバインドで関数をつないであげれば、ifとかelseとかbreakとか書かなくてもよい。(composableうんぬんは、tanakhさんがつぶやいていた話です。多分もっと高尚なお話だったと思いますが、自分が理解した限りだとこんな話。。のはず。)
functorやapplicativeと比べて、モナドがやたらと注目されている理由は、この3つの中で最も抽象度が高いからという理由もあるが、上のようなデザインパターンとして使用できるからというのが大きいのだと思う。
最後に、今回実装したモナドがモナド則を満たしていることを確認する。
適当に値を与えて確認する。1つ1つやってもいいけど、せっかくなので最近覚えたlist applicativeを使って複数個のパターンを一気に確認する。
nums = [-5, 0, 5]
ck = [YES, NO, Unknown (-5), Unknown 0, Unknown 5]
funcs = [passTest (divisable 400),
failTest (divisable 100),
determine (divisable 4)
]
assertLeftIdentity = (\f x -> (return x >>= f) == f x) <$> funcs <*> nums
assertRightIdentity = (\m -> (m >>= return) == m) <$> ck
assertAssociativity = (\f g m -> ((m >>= f) >>= g)
== (m >>= (\x -> f x >>= g))) <$> funcs <*> funcs <*> ck
イケてた。モナドイケイケ~~。
0 件のコメント:
コメントを投稿