5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

pipes-parseの概要

Posted at

pipes-parseとは

ストリーミングライブラリである pipes の上に構成したパーサを書くためのライブラリ。「入力をパースしてから出力してくれる Pipe」ではないことに注意が必要。つまり、 Data.Conduit.Attoparsec のように Conduit になっているわけではないので、同じ感覚で >-> で間に挟んで使おうとすると面食らうだろう。

読むべきドキュメント

pipesが正しいListT相当のモノを提供してますよって前提を理解した上で、Haskell for allのエントリチュートリアルを読む。yesod webの反応も読むのにいい。でもって、pipes-groupのチュートリアルも肝になるので読むのを忘れちゃいけない。

簡単なまとめ

最初に書いたように Pipe の上に別のフレームワークを作っているのが pipes-parser であり、それぞれ ProducerLensParser(StateT)の3つを使ってPipeチックなツールチェインを構成している。それぞれの関係は以下。

  • ProducerLensviewでつなぐとProducerになる
  • LensParserzoomでつなぐとParserになる
  • ProducerParserrunStateTでつなぐとパースした結果と残りの要素を含むProducerが得られる

まず、なぜParserStateTかについて。パーザで必要になるのが peek であり、これを実現するために StateT を使ってる。パースする間はProducerを状態として持っておいて、読み込んだ要素を戻すときはProducerを変更してやるっていうすごく簡単なアイデア。

でもって、Lensの役割。Producerとくっつけるときは実はLensである必要がなく、Getterで十分である。用はProducerの出力をマップできればいいだけ。pipes-attoparsecparsedなんかはこの能力しか持ってない。パース後の構造を文字列に戻してsetする術を知らないので。

Lensが活躍するのはParserとくっつけるとき。ストリームに戻された内容をLensのsetterを通してProducerへ書き戻すことができる。Intを要求するパーサがあるとき、Producer StringからProducer Intを取り出すLensがあれば、Intが戻されたときにLensのセッターを通してProducer Stringへ要素を戻せるってスンポー。

LensParserにくっつけるか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 に戻ってくれて、非常にストレスなく利用することができる。

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?