パーサーは一度に 1 行ずつ動作するため、attoparsec-iteratee を使用する必要さえありません。これを次のように書きます。
import Data.Iteratee as I
import Data.Iteratee.Char
import Data.Attoparsec as A
parser :: Parser ParseOutput
type POut = Either String ParseOutput
processLines :: Iteratee ByteString IO [POut]
processLines = joinI $ (enumLinesBS ><> I.mapStream (A.parseOnly parser)) stream2list
これを理解するための鍵は「enumeratee」です。これは、ストリーム コンバーターの iteratee 用語にすぎません。あるストリーム タイプのストリーム プロセッサ (iteratee) を取り、別のストリームで動作するように変換します。enumLinesBS
との両方mapStream
が列挙型です。
パーサーを複数行にマップするmapStream
だけで十分です。
i1 :: Iteratee [ByteString] IO (Iteratee [POut] IO [POut]
i1 = mapStream (A.parseOnly parser) stream2list
ネストされた iteratee は、これが のストリームを のストリームに変換し[ByteString]
、[POut]
最後の iteratee (stream2list) が実行されると、そのストリームを として返すことを意味します[POut]
。lines
したがって、 のストリームを作成するには[ByteString]
、に相当する iteratee が必要ですenumLinesBS
。
i2 :: Iteratee ByteString IO (Iteratee [ByteString] IO (Iteratee [POut] m [POut])))
i2 = enumLinesBS $ mapStream f stream2list
しかし、この関数はすべてネストされているため、非常に扱いにくいものです。私たちが本当に必要としているのは、出力をストリーム コンバーター間で直接パイプし、最後にすべてを 1 つの iteratee に単純化する方法です。これを行うには、 、 、および を使用joinI
し(><>)
ます(><>)
。
e1 :: Iteratee [POut] IO a -> Iteratee ByteString IO (Iteratee [POut] IO a)
e1 = enumLinesBS ><> mapStream (A.parseOnly parser)
i' :: Iteratee ByteString IO [POut]
i' = joinI $ e1 stream2list
これは、e1
インラインで上に書いた方法と同じです。
それでも重要な要素は残っています。この関数は、解析結果をリストで返すだけです。通常、結果を折り畳みと組み合わせるなど、何か他のことをしたいと思うでしょう。
編集:Data.Iteratee.ListLike.mapM_
コンシューマーの作成に役立つことがよくあります。その時点で、ストリームの各要素は解析結果であるため、それらを印刷したい場合は使用できます
consumeParse :: Iteratee [POut] IO ()
consumeParse = I.mapM_ (either (\e -> return ()) print)
processLines2 :: Iteratee ByteString IO ()
processLines2 = joinI $ (enumLinesBS ><> I.mapStream (A.parseOnly parser)) consumeParse
これにより、成功した解析のみが出力されます。エラーを STDERR に報告したり、他の方法で処理したりすることも簡単にできます。