Page List

Search on the blog

2011年7月7日木曜日

モナドを実装する

 今日はモナド実装にチャレンジ。
モナドの機能は、contextualな値に関数を適用すること。
functorやapplicativeも似たようなことを行うが、モナドのいいところは演算子のつなぎ目をきれいに書けるところ。

 とりあえず、書いてみる。まずモナドクラスのインスタンスにする型を定義する。
Maybe型を応用して、即成功、即失敗、判断を後回しにする、という3つのcontextを持つ型を定義する。
  1. data Check a = YES | NO | Unknown a deriving (Show, Eq)  
 この型を利用するために以下の関数を定義する。
  1. passTest f x  = if f x == True then YES else Unknown x  
  2. failTest f x  = if f x == True then NO else Unknown x  
  3. determine f x = if f x == True then YES else NO  
 さらに、この型をモナドインスタンスとして実装する。
  1. instance Monad Check where  
  2.  return x = Unknown x  
  3.  YES >>= _ = YES  
  4.  NO >>= _ = NO  
  5.  Unknown x >>= f = f x  
 これを使うと、ややこしい制御フローがきれいに書ける。例えば、うるう年判定をするプログラム。やたらとif-elseが多い。
  1. isLeapYear x = if x `mod` 400 == 0  
  2.               then True  
  3.               else if x `mod` 100 == 0  
  4.                       then False  
  5.                       else if x `mod` 4 == 0  
  6.                               then True  
  7.                               else False  
 モナドを使うときれいに書ける。
  1. divisable y x = x `mod` y == 0  
  2. isLeapYear x = return x >>= passTest (divisable 400)  
  3.                       >>= failTest (divisable 100)  
  4.                       >>= determine (divisable 4)  
 モナドは、制御フローを表に出さずに演算を繋げることができるデザインパターンと考えることもできる。見てのとおり制御フローをcomposableな要素として部品化することができ、if-else-break文のボイラープレート除去を行うことができる。条件が増えたら、新しいバインドで関数をつないであげれば、ifとかelseとかbreakとか書かなくてもよい。(composableうんぬんは、tanakhさんがつぶやいていた話です。多分もっと高尚なお話だったと思いますが、自分が理解した限りだとこんな話。。のはず。)

functorやapplicativeと比べて、モナドがやたらと注目されている理由は、この3つの中で最も抽象度が高いからという理由もあるが、上のようなデザインパターンとして使用できるからというのが大きいのだと思う。

最後に、今回実装したモナドがモナド則を満たしていることを確認する。
適当に値を与えて確認する。1つ1つやってもいいけど、せっかくなので最近覚えたlist applicativeを使って複数個のパターンを一気に確認する。
  1. nums  = [-5, 0, 5]  
  2. ck    = [YES, NO, Unknown (-5), Unknown 0, Unknown 5]  
  3. funcs = [passTest (divisable 400),  
  4.         failTest (divisable 100),  
  5.         determine (divisable 4)  
  6.        ]  
  7.   
  8. assertLeftIdentity  = (\f x -> (return x >>= f) == f x) <$> funcs <*> nums  
  9. assertRightIdentity = (\m -> (m >>= return) == m) <$> ck  
  10. assertAssociativity = (\f g m -> ((m >>= f) >>= g)  
  11.                          == (m >>= (\x -> f x >>= g))) <$> funcs <*> funcs <*> ck  

イケてた。モナドイケイケ~~。

0 件のコメント:

コメントを投稿