19

Haskell で iteratee ライブラリを使用する方法を理解しようとしています。これまでに目にしたすべての記事は、どのように iteratee を構築できるかについての直感を構築することに焦点を当てているように見えます。iteratees のソース コードを見ることは、私にとってあまり価値がありませんでした。

行から末尾の空白を削除するこの関数があるとしましょう:

import Data.ByteString.Char8

rstrip :: ByteString -> ByteString
rstrip = fst . spanEnd isSpace

私がやりたいことは、これを iteratee にし、ファイルを読み込んで、各行から末尾の空白を取り除いて別の場所に書き出すことです。iteratees を使用してそれを構造化するにはどうすればよいですか? Data.Iteratee.Char に関数があり、これに組み込むことができますが、上記の関数を使用する必要があるかどうか、または上記の関数を iteratee に再パッケージ化する方法がenumLinesBSわかりません。mapChunksconvStream

4

1 に答える 1

16

コードだけが必要な場合は、次のとおりです。

procFile' iFile oFile = fileDriver (joinI $
   enumLinesBS ><>
   mapChunks (map rstrip) $
   I.mapM_ (B.appendFile oFile))
   iFile

解説:

これは 3 段階のプロセスです。まず生のストリームを行のストリームに変換し、関数を適用してその行のストリームを変換し、最後にストリームを消費します。rstripは中盤なので、ストリームトランスフォーマー(Enumeratee)を作っていきます。

または のいずれmapChunksかを使用できますconvStreamが、mapChunksより簡単です。違いは、より一般的なmapChunksのに対し、チャンクの境界を越えることができないことです。基礎となる実装を公開しないため、convStream私は好みますが、十分な場合、結果のコードは通常より短くなります。convStreammapChunks

rstripE :: Monad m => Enumeratee [ByteString] [ByteString] m a
rstripE = mapChunks (map rstrip)

の余分な点に注意してmapくださいrstripE。外側のストリーム (rstrip への入力) は type[ByteString]であるため、それにマップrstripする必要があります。

比較のために、convStream で実装した場合は次のようになります。

rstripE' :: Enumeratee [ByteString] [ByteString] m a
rstripE' = convStream $ do
  mLine <- I.peek
  maybe (return B.empty) (\line -> I.drop 1 >> return (rstrip line)) mLine

これはより長く、より多くの行が使用できる場合でも、rstrip 関数を一度に 1 行にしか適用しないため、効率が低下します。mapChunksバージョンに近い、現在利用可能なすべてのチャンクで作業することが可能です。

rstripE'2 :: Enumeratee [ByteString] [ByteString] m a
rstripE'2 = convStream (liftM (map rstrip) getChunk)

とにかく、ストリッピング enumeratee が利用できるので、enumLinesBSenumeratee で簡単に合成できます:

enumStripLines :: Monad m => Enumeratee ByteString [ByteString] m a
enumStripLines = enumLinesBS ><> rstripE

合成演算子><>は、矢印演算子と同じ順序に従います>>>enumLinesBSストリームを行に分割してから、それらをrstripE取り除きます。これで、コンシューマー (通常の iteratee) を追加するだけで完了です。

writer :: FilePath -> Iteratee [ByteString] IO ()
writer fp = I.mapM_ (B.appendFile fp)

processFile iFile oFile =
  enumFile defaultBufSize iFile (joinI $ enumStripLines $ writer oFile) >>= run

関数は、単純にファイルを列挙し、結果の iteratee を実行するためのfileDriverショートカットです (残念ながら、引数の順序は enumFile から切り替えられます)。

procFile2 iFile oFile = fileDriver (joinI $ enumStripLines $ writer oFile) iFile

補遺: これは、convStream の追加機能が必要になる状況です。2 行ごとに 1 行に連結するとします。使用できませんmapChunks。チャンクがシングルトン要素である場合を検討してください[bytestring]mapChunks次のチャンクにアクセスする方法を提供しないため、これと連結するものは他にありません。ただし、それはconvStream簡単です:

concatPairs = convStream $ do
  line1 <- I.head
  line2 <- I.head
  return $ line1 `B.append` line2

これは適用可能なスタイルでさらに見栄えがよく、

convStream $ B.append <$> I.head <*> I.head

convStream提供された iteratee を使用してストリームの一部を継続的に消費し、変換されたバージョンを内部のコンシューマーに送信すると考えることができます。各ステップで同じ iteratee が呼び出されるため、これでも一般的ではない場合があります。その場合、unfoldConvStream連続する反復間で状態を渡すために使用できます。

convStreamまたunfoldConvStream、ストリーム処理 iteratee はモナド トランスフォーマーであるため、モナド アクションも可能です。

于 2011-07-11T02:21:15.467 に答える