我正在尝试使用attoparsec解析器编写Conduit
。具体来说,给定parseOne :: Parser T
,我想构造一个Conduit ByteString m T
,重复将解析器应用于输入并流式传输结果。
attoparsec-conduit提供sinkParser
将Parser
变为Sink
,但如何将此Sink
变为Conduit
?我正在寻找的功能如下:
conduitSink :: (Resource m) => Sink a m b -> Conduit a m b
重复将数据输入Sink
,随着时间的推移生成每个结果。看起来它可以很容易地写成手动循环,但我想知道是否有更好的方法。
导管库中缺乏这个看似显而易见的功能让我觉得我可能做错了什么;有没有更好的方法来实现这一目标?用例是将原始字节转换为基于消息的网络协议的解析形式,由管道的后续阶段处理。由于blaze-builder-conduit,我已经有相反的方向(即Conduit T m ByteString
),所以这似乎是构建事物的最自然的方式。
答案 0 :(得分:6)
您需要使用SequencedSink
系统;它使用水槽和跟踪状态来生成水槽生产者重复应用的管道。
您创建的接收器已经过优化,可以逐步解析一个值,这将是导管序列末尾的结果。
由于您希望将其作为管道管道的一部分,并且传入的ByteString
的每个块可能会或可能不会与您的解析器匹配一次或多次,因此您需要注意使其更加精细 - 粗略控制解析过程,在接收器的每个应用程序之间传递不完整解析的状态。
假设,例如,您的解析器解析[--]
或[----]
等,而T
是Int
表示解析的破折号数,则需要跟踪解析器的状态如下所示:
Input chunk Sink result - Data.Conduit.SequencedSinkResponse
[--][---] Emit Nothing [2, 3]
[---][--- Emit (Just #func) [3]
--------- Emit (Just #func) []
] Emit Nothing [12]
Stop
在这种情况下,我使用Maybe (ByteString -> Data.Attoparsec.ByteString.Result)
作为传递状态;根据具体情况,不同的数据类型可能更合适。
需要这种明确的流处理来维持管道的管道性质;让解析器管道成为“瓶颈”,总是在等待足够数据来满足解析器,这将是一个主要的性能下沉。
使用可用的ResourceT
monad接口,所需接收器的实现应该相当简单。
编辑:简单地在循环中应用您的接收器确实是最简单的解决方案,但如果您的解析器解析通常最终在字节块的边界上的短片段,它将具有稍微不同的性能特征