もなもなモナド ReaderモナドとWriterモナドの合成
モナドの合成はさっぱりわからないままだったので、試しにReaderモナドとWriterモナドの合成をやってみた。
リーフは数値、ブランチにはラベルがついてる木構造データを対象に、
の順番で変更が少ないように実装してみたつもり。それぞれmain1,main2,main3に対応する。これでちゃんと動く理屈はあまりちゃんとわかってない。わかったら後で書く。
以下ソース
data T = B String [T] | L Int sample = B "root" [L 5,B "child" [L 1,L 10],L 2] -- main1 = sum_leaf sample sum_leaf (L n) = n sum_leaf (B _ children) = sum (map sum_leaf children) -- main2 = runWriter $ sum_leaf_w sample sum_leaf_w :: T -> Writer [Int] Int sum_leaf_w (L n) = do tell [n] return n sum_leaf_w (B _ children) = do n_list <- mapM sum_leaf_w children return (sum n_list) -- main3 = runReader (runWriterT (sum_leaf_wr sample)) "" sum_leaf_wr :: T -> WriterT [String] (Reader String) Int sum_leaf_wr (L n) = do parent <- lift ask tell ["parent:" ++ parent ++ ", " ++ show n] return n sum_leaf_wr (B parent children) = do n_list <- mapM (liftM (local (const parent)) sum_leaf_wr) children return (sum n_list)
なんとなく、liftとliftMが対応している、ような気がする。
実行するとこうなる。
Main> main1 18 Main> main2 (18,[5,1,10,2]) Main> main3 (18,["parent:root, 5","parent:child, 1","parent:child, 10","parent:root, 2"])
最初、sum_leaf_wrの中を
mapM (\c->liftM (local (const parent)) (sum_leaf_wr c)) children
と書いてて型があわなくてはまった。むずかしい。
あれーでも、なんでこれで型があってるんだろう。
Main> :t sum_leaf_wr sum_leaf_wr :: T -> WriterT [String] (Reader String) Int Main> :t liftM liftM :: (Monad m) => (a1 -> r) -> m a1 -> m r
T->m ..とm a1って整合するの??
参考サイト
追記 いろいろおしえてもらえた
ありがとうございます。
西川さんからのトラックバックによると、上の例だとliftとliftMは必要ないらしい。
main4 = runReader (runWriterT (sum_leaf_wr' sample)) "" sum_leaf_wr' :: T -> WriterT [String] (Reader String) Int sum_leaf_wr' (L n) = do parent <- ask tell ["parent:" ++ parent ++ ", " ++ show n] return n sum_leaf_wr' (B parent children) = do n_list <- mapM (\c-> local (const parent) (sum_leaf_wr' c)) children return (sum n_list)
*Main> main4 (18,["parent:root, 5","parent:child, 1","parent:child, 10","parent:root, 2"])
おお。ほんとだ。MonadReaderのインスタンスと合成*1したWriterTはもとからMonadReaderのインスタンス、ってことか、な?
というより、二つのモナドを入れ子的に考える必要はなくて、合成したモナドそのものが両方のモナドの役割を果せる*2ってのが嬉しさなわけか。そこを把握できてなかったよ。
とすると、ReaderとWriterの型の変換順を変えてもコードはほとんど変更する必要ないんだな。
main4' = runWriter (runReaderT (sum_leaf_rw sample) "") -- ここの順番変更 sum_leaf_rw :: T -> ReaderT String (Writer [String]) Int -- 型の順番変更 sum_leaf_rw (L n) = do parent <- ask tell ["parent:" ++ parent ++ ", " ++ show n] return n sum_leaf_rw (B parent children) = do n_list <- mapM (\c-> local (const parent) (sum_leaf_rw c)) children return (sum n_list)
*Main> main4' (18,["parent:root, 5","parent:child, 1","parent:child, 10","parent:root, 2"])
おお。組み合わせるモナドによっては意味が変わっちゃう場合があるらしいので注意が必要だけど、ReaderとWriterなら気にしなくてよい、と。
shelarcyさんにも言及してもらえました(http://page.freett.com/shelarcy/log/2007/diary_11.html#simotuki302007)。
「本物のプログラマはHaskellを使う(http://itpro.nikkeibp.co.jp/article/COLUMN/20060915/248215/?ST=develop)」の次回の予定がモナド変換子の話だそうで。そこまで寝て待とう。