Haskellで躓く理由の一つにPerl,Pythonみたいなノリでprintデバッグしにくいってのがありますね。
Haskell の printf デバッグ / tnomuraのブログでprintデバッグをやっていたので、これをWriterTで書きなおしてみた(最近モナド変換子を理解したので)。
元のコードは階乗計算ですね。
fact 0 = return 1 fact n = do print n n1 <- fact (n-1) return (n * n1)
WriterTモナド変換子を積むとこうなる。
import Control.Monad.Writer factT :: Int -> WriterT [Int] IO Int factT 0 = tell [0] >> return 1 factT n = do tell [n] n1 <- factT (pred n)
tellでログるので、runWriterTで実行する。
*Main> runWriterT $ factT 5 (120,[5,4,3,2,1,0]) *Main> execWriterT $ factT 5 [5,4,3,2,1,0] *Main> liftM fst $ runWriterT $ factT 5 120
do記法で何故WriterTとIOが剥がれるのかちょっとよくわからなかったんだけど、bindの定義を見たら
instance (Monoid w, Monad m) => Monad (WriterT w m) where return a = WriterT $ return (a, mempty) m >>= k = WriterT $ do (a, w) <- runWriterT m (b, w') <- runWriterT (k a) return (b, w `mappend` w') fail msg = WriterT $ fail msg
ってなっていたので、型だけちゃんとあわせてあとはtellをはさめばロギングしてくれるようになっているのねと(便利だ)。
階乗計算の例だったらIOモナドを通さなくてもWriterモナドを使えばピュアな計算にログ機能を付加することもできる。
import Control.Monad.Writer factW :: Int -> Writer [Int] Int factW 0 = tell [0] >> return 1 factW n = do tell [n] n1 <- factW (pred n) return $ n * n1
実行する場合はrunWriter
*Main> runWriter $ factW 5 (120,[5,4,3,2,1,0])
こういうのは、理屈がわからなくてもいいからとりあえず身体で覚える的に早い段階で説明されててもいいのかなぁなんて思った。