由于您的解析器一次只运行一行,因此您甚至不需要使用 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”,它只是流转换器的迭代项。它采用一种流类型的流处理器(迭代器)并将其转换为与另一种流一起使用。enumLinesBS
和都是mapStream
枚举数。
要将解析器映射到多行,mapStream
就足够了:
i1 :: Iteratee [ByteString] IO (Iteratee [POut] IO [POut]
i1 = mapStream (A.parseOnly parser) stream2list
嵌套的迭代器只是意味着这会将流转换为流[ByteString]
,[POut]
并且当运行最终迭代器(stream2list)时,它会将该流返回为[POut]
。所以现在你只需要 iteratee 等价物lines
来创建那个流[ByteString]
,这就是enumLinesBS
:
i2 :: Iteratee ByteString IO (Iteratee [ByteString] IO (Iteratee [POut] m [POut])))
i2 = enumLinesBS $ mapStream f stream2list
但是由于所有的嵌套,这个函数使用起来非常笨拙。我们真正想要的是一种直接在流转换器之间管道输出的方法,最后将所有内容简化为单个迭代。为此,我们使用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 报告错误,或以其他方式处理它们。