7

我正在尝试Conduit使用attoparsec解析器编写一个。具体来说,给定parseOne :: Parser T,我想构造一个Conduit ByteString m T重复地将解析器应用于输入并流式传输结果的方法。

attoparsec-conduit提议sinkParser将 aParser变成 a Sink,但我怎样才能将其Sink变成 a Conduit?我正在寻找的是一个类似的功能:

conduitSink :: (Resource m) => Sink a m b -> Conduit a m b

它反复将数据输入到 中Sink,同时产生每个结果。看起来它可以很容易地编写为手动循环,但我想知道是否有更好的方法。

管道库中缺少这个看似显而易见的功能让我觉得我可能做错了什么;有没有更好的方法来做到这一点?用例是将原始字节转换为基于消息的网络协议的解析形式,以供管道的后续阶段处理。Conduit T m ByteString多亏了blaze-builder-conduit ,我已经有了相反的方向(即) ,所以这似乎是构建事物的最自然的方式。

4

1 回答 1

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)作为传递状态;根据情况,不同的数据类型可能更合适。

需要这种明确的流处理来维持管道的管道性质;让解析器管道成为“瓶颈”,总是等待足够的数据来逐块满足解析器,这将是一个主要的性能下降。

使用可用的ResourceTmonad 接口,所需接收器的实现应该是相当简单的。

编辑:简单地在一个循环中应用你的接收器确实是最简单的解决方案,但如果你的解析器解析通常最终在字节块边界上的短片段,它将具有稍微不同的性能特征。

于 2012-01-28T04:52:22.073 回答