10

Iteratees について少し学ぶために、Data.Iteratee と Data.Attoparsec.Iteratee を使用して、私が作成した単純なパーサーを再実装したかったのです。私はかなり困惑しています。以下に、ファイルから1行を解析できる簡単な例を示します。私のパーサーは一度に 1 行ずつ読み取るため、それが完了するまで iteratee に行を供給する方法が必要です。これをグーグルで見つけたものはすべて読みましたが、反復子/列挙子に関する多くの資料はかなり高度です。これは重要なコードの部分です。

-- There are more imports above.
import Data.Attoparsec.Iteratee
import Data.Iteratee (joinI, run)
import Data.Iteratee.IO (defaultBufSize, enumFile)

line :: Parser ByteString -- left the implementation out (it doesn't check for 
                             new line)

iter = parserToIteratee line

main = do
    p <- liftM head getArgs
    i <- enumFile defaultBufSize p $ iter
    i' <- run i
    print i'

この例では、複数行のファイルから 1 行を解析して出力します。元のスクリプトは、パーサーを ByteStrings のリストにマッピングしていました。ですから、ここでも同じことをしたいと思います。私enumLinesは Iteratee で見つけましたが、一生それを使用する方法を理解することはできません。多分私はその目的を誤解していますか?

4

1 に答える 1

15

パーサーは一度に 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 に報告したり、他の方法で処理したりすることも簡単にできます。

于 2011-06-15T18:31:51.773 に答える