pipes-parseとは
ストリーミングライブラリである pipes の上に構成したパーサを書くためのライブラリ。「入力をパースしてから出力してくれる Pipe
」ではないことに注意が必要。つまり、 Data.Conduit.Attoparsec のように Conduit
になっているわけではないので、同じ感覚で >->
で間に挟んで使おうとすると面食らうだろう。
読むべきドキュメント
pipesが正しいListT相当のモノを提供してますよって前提を理解した上で、Haskell for allのエントリやチュートリアルを読む。yesod webの反応も読むのにいい。でもって、pipes-groupのチュートリアルも肝になるので読むのを忘れちゃいけない。
簡単なまとめ
最初に書いたように Pipe
の上に別のフレームワークを作っているのが pipes-parser
であり、それぞれ Producer
、Lens
、Parser
(StateT
)の3つを使ってPipe
チックなツールチェインを構成している。それぞれの関係は以下。
-
Producer
とLens
をview
でつなぐとProducer
になる -
Lens
とParser
をzoom
でつなぐとParser
になる -
Producer
とParser
をrunStateT
でつなぐとパースした結果と残りの要素を含むProducer
が得られる
まず、なぜParser
がStateT
かについて。パーザで必要になるのが peek
であり、これを実現するために StateT
を使ってる。パースする間はProducer
を状態として持っておいて、読み込んだ要素を戻すときはProducer
を変更してやるっていうすごく簡単なアイデア。
でもって、Lens
の役割。Producer
とくっつけるときは実はLens
である必要がなく、Getter
で十分である。用はProducer
の出力をマップできればいいだけ。pipes-attoparsecのparsed
なんかはこの能力しか持ってない。パース後の構造を文字列に戻してsetする術を知らないので。
Lens
が活躍するのはParser
とくっつけるとき。ストリームに戻された内容をLens
のsetterを通してProducer
へ書き戻すことができる。Int
を要求するパーサがあるとき、Producer String
からProducer Int
を取り出すLens
があれば、Int
が戻されたときにLens
のセッターを通してProducer String
へ要素を戻せるってスンポー。
Lens
をParser
にくっつけるかProducer
にくっつけるかで何が変わるかは以下のように比較できる。
mylens :: Lens' (Producer String IO x) (Producer Int IO x)
mylens = iso fw bw
where
fw p = do
lift (next p) >>= \case
Left r -> return r
Right (x, p') -> do
lift $ putStrLn ("forward:" ++ x)
yield (read x)
fw p'
bw p = do
lift (next p) >>= \case
Left r -> return r
Right (x, p') -> do
lift $ putStrLn ("backward:" ++ show x)
yield (show x)
bw p'
myparser :: Parser Int IO (Maybe Int)
myparser = do
unDraw 256
peek >>= \case
Just x -> lift $ putStrLn (show x)
Nothing -> lift $ putStrLn "Nothing"
draw
byView :: IO ()
byView = runparser myparser (P.each ["1", "2", "3"] ^. mylens)
byZoom :: IO ()
byZoom = runparser (zoom mylens myparser) (P.each ["1", "2", "3"])
runparser
:: (Show a, Show b) =>
StateT (Producer a IO ()) IO b -> Producer a IO () -> IO ()
runparser pa pr = do
(r, pr') <- runStateT pa pr
print r
r' <- evalStateT draw pr'
print r'
λ> byView
256
Just 256
forward:1
Just 1
λ> byZoom
256
Just 256
forward:1
backward:1
Just "1"
で、このパーザのツールチェーンを Pipe
と使いたければどうするのかってのが pipes-groupのチュートリアル に書いてある。基本的に Parser
だと一回パースする分はメモリに取るしかないので、このチュートリアルで一番やっちゃダメだって書いてあるコードで使うしかないと思う。
いい感じで使えるのが Lens
が提供されていてこいつを使いたい場合で、Producer
をある部分まで yield
して残りは return
値として返すような Producer
に変換するような Lens
があるとメモリに貯めずにストリーミングができて、この構造が Producer
の入れ子になるので FreeT
モナドが登場してくる。pipes-textとかはFreeT
の関数を提供してくれてるので、pipes-groupのチュートリアルにあるようにこいつで FreeT
の形式でぶった切ってから concats
でまとめあげるとまた Producer
に戻ってくれて、非常にストレスなく利用することができる。