我从Data.Conduit构建了以下类型:
type Footers = [(ByteString, ByteString)]
type DataAndConclusion = ConduitM () ByteString IO Footers
第二种类型的想法是“产生大量ByteStrings,如果你可以产生所有这些,返回一个页脚”。条件是因为管道由下游功能管理,因此DataAndConclusion的使用者可能不需要消耗其所有项目,并且在这种情况下将不会达到返回。这正是我需要的行为。但当到达源代码的结尾时,我想拥有生成的Footers。这将是有用的,例如,如果DataAndConclusions正在递增地计算MD5,并且只有在整个消息由下游处理时才需要这样的MD5(例如,下游可能只是通过网络发送它,但它没有意义如果套接字在下游发送最后一块之前关闭,则完成计算并发送MD5。
所以,基本上我想要使用此签名来使用DataAndConclusions:
type MySink = Sink ByteString IO ()
mySink :: MySink
mySink = ...
difficultFunction :: ConduitM () a2 m r1 -> ConduitM a2 Void m r2 -> m (Maybe r1)
问题是,有没有办法实现“hardFunction”?怎么样?
答案 0 :(得分:2)
如果DataAndConclusions是,那么这将非常有用 逐步计算MD5,只有在需要时才需要这样的MD5 整个邮件由下游处理
在这种情况下,您可以在StateT
下的ConduitM
层中累积正在进行的MD5计算,而不是依赖上游管道的返回值,并在运行管道后访问它。 / p>
谜题的另一部分是检测到生产者已经完成了第一次。 Sink
可以检测await
次呼叫中的上游输入结束。您可以编写Sink
来通知您自己的结果类型中的上游终止,可能使用Maybe
。
但是如果给你一个Sink
还没有这样做呢?我们需要像Sink i m r -> Sink i m (Maybe r)
这样的函数。 “如果Sink
可能提前停止,则返回一个新的Sink
,如果上游完成,则返回Nothing
”。但我不知道如何写这个功能。
修改:此渠道在检测到上游终止时将IORef
设置为True
:
detectUpstreamClose :: IORef Bool -> Conduit i IO i
detectUpstreamClose ref = conduit
where
conduit = do
m <- await
case m of
Nothing -> liftIO (writeIORef ref True)
Just i -> do
yield i
conduit
可以在管道中插入 detectUpstreamClose
,之后可以检查IORef
。
答案 1 :(得分:2)
应该有一个很好的解决方案,但我无法使用ConduitM
原语构建它。带签名的东西
ConduitM i a m r1 -> ConduitM a o m r2 -> ConduitM i o m (Maybe r1, r2)
看起来像带有此签名的原始函数将是管道库的一个很好的补充。
尽管如此,@ danidiaz关于StateT
的建议引导我采用以下通用解决方案,将整个计算内部提升到WriterT
,以便记住第一个管道的输出,如果达到:
import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.Writer
import Data.Conduit
import Data.Monoid
import Data.Void
difficultFunction :: (Monad m)
=> ConduitM () a2 m r1 -> ConduitM a2 Void m r2
-> m (r2, Maybe r1)
difficultFunction l r = liftM (fmap getLast) $ runWriterT (l' $$ r')
where
l' = transPipe lift l >>= lift . tell . Last . Just
r' = transPipe lift r
(未经测试!)