Parsec で複合モナドを作る
print デバッグ
Parsec のパーサモナド ParsecT s u m a はモナドトランスフォーマーだ。これは、ParsecT モナドの中で、他のモナドの関数を使えるということを意味している。それに関してすぐに思いつくのは、IO モナドを使った print デバッグをパーサモナドで使えるかもしれないということだ。
複合モナドを作るといっても、モナドトランスフォーマーがあれば簡単だ。ParsecT を使ったパーサモナドの型指定のとき、ParsecT String () IO String の様に、ParsecT s u m a の m のところに IO を指定するだけでいい。
あとは、IO モナドの putStrLn 関数を ParsecT 複合モナドの中で使いたいときは、lift . putStrLn のように lift 関数と putStrLn 関数の合成関数をつくれば、lift . putStrLn 関数を ParsecT 複合モナドの関数として使うことができる。
Control.Monad.Trans モジュール
複合モナドについて概念を説明しても伝わらないと思うので実例をやってみる。
まず、ParsecT を複合モナドとして使うために、Text.Parsec モジュールに加えて、Control.Monad.Trans モジュールの lift 関数をインポートする。
ParsecT パーサモナドの複合モナドを使うためには lift 関数が必要だが、Text.Parsec モジュールをインポートしただけでは lift 関数はインポートされていないからだ。
Prelude> import Text.Parsec Prelude Text.Parsec> import Control.Monad.Trans (lift) Prelude Text.Parsec Control.Monad.Trans> :t lift lift :: (Monad m, Control.Monad.Trans.Class.MonadTrans t) => m a -> t m a
runPT 関数
もう一つの注意点は、ParsecT 複合モナドで作ったモナドプログラムを実行させるためには runPT 関数を使わなければならないということだ。一連の Parsec 関連の記事で用いた parseTest 関数は複合モナドには使えない。
準備ができたので、さっそく IO モナドの関数である putStrLn 関数を ParsecT 複合モナドの中で lift .putStrLn 関数として使ってみよう。
Prelude Text.Parsec Control.Monad.Trans> runPT ((lift . putStrLn) "hello") () "SourceName" "foo" hello Right ()
大成功。
入力バッファの内容を表示する
しかし、これだけでは能がないので、"hello, world" という文字列に string "hello" をマッチさせ、その時の入力バッファの内容の変化を putStrLn で表示してみよう。ParsecT モナドで入力バッファの内容を取り出す関数は getInput 関数だ。
Prelude Text.Parsec Control.Monad.Trans> :t getInput getInput :: Monad m => ParsecT s u m s
実行結果は次のようになる。インプットバッファの文字列から string "hello" のマッチが行われたあとは "hello" が消費されて消えていることが分かる。
Prelude Text.Parsec Control.Monad.Trans> runPT (do x <- getInput; (lift . putStrLn) x; string "hello"; x <- getInput; (lift. putStrLn) x) () "SourceName" "hello, world" hello, world , world Right ()
分かりやすいように do 記法で書いたが >>= 演算子を活用するともっとコンパクトに書ける。
Prelude Text.Parsec Control.Monad.Trans> runPT (getInput >>= lift . putStrLn >> string "hello" >> getInput >>= lift . putStrLn) () "SourceName" "hello, world" hello, world , world Right ()
プログラムをファイルに記述するときは、きちんと型指定しないといけないのでやってみた。(行頭の .. はスペースに変える。)
Prelude Text.Parsec Control.Monad.Trans> :{ Prelude Text.Parsec Control.Monad.Trans| foo :: ParsecT String () IO () Prelude Text.Parsec Control.Monad.Trans| foo = do Prelude Text.Parsec Control.Monad.Trans| .. x <- getInput Prelude Text.Parsec Control.Monad.Trans| .. lift $ putStrLn x Prelude Text.Parsec Control.Monad.Trans| .. string "hello" Prelude Text.Parsec Control.Monad.Trans| .. x <- getInput Prelude Text.Parsec Control.Monad.Trans| .. lift $ putStrLn x Prelude Text.Parsec Control.Monad.Trans| :}
実行例は次のようになる。
Prelude Text.Parsec Control.Monad.Trans> runPT foo () "SourceName" "hello, world" hello, world , world Right ()
複合モナドというと難しく感じるが、要するに ParsecT の型指定のときに ParsecT s u m a の m のところに IO を指定すれば、StateT 複合モナドの中で lift . putStrLn 関数が使えるようになると覚えれば簡単だ。こういうものは文章を読んで理解しようとするよりは手っ取り早くスニペットを作って実験してみたほうが分かりやすい。