将一个 ResumableSource 附加到另一个

Appending one ResumableSource to another

当一个 ResumableSource 不是 Monad 的明确实例时,如何将它们附加到另一个?下面是一个玩具示例 - aMonad 约束,而 b 没有。所以我们可以追加 a's 而不是 b's:

Prelude>  import Data.Conduit
Prelude Data.Conduit> import Data.ByteString as BS
Prelude Data.Conduit BS> import Control.Monad.Trans.Resource
Prelude Data.Conduit BS Control.Monad.Trans.Resource> let a = newResumableSource (yield (BS.pack [5])) -- this one has monad constraint
Prelude Data.Conduit BS Control.Monad.Trans.Resource> :t a
a :: Monad m => ResumableSource m ByteString
Prelude Data.Conduit BS Control.Monad.Trans.Resource> :t a >> a
a >> a
  :: (Monad m, Monad (ResumableSource m)) =>
     ResumableSource m ByteString
Prelude Data.Conduit BS Control.Monad.Trans.Resource> let b = undefined :: ResumableSource (ResourceT IO) ByteString
Prelude Data.Conduit BS Control.Monad.Trans.Resource> :t b >> b

<interactive>:1:3:
    No instance for (Monad (ResumableSource (ResourceT IO)))
      arising from a use of ‘>>’
    In the expression: b >> b

我问它的原因是因为我有一个 HTTP ResumableSource 与上面的 b 类型相同,我想在将其送入接收器之前在其前面添加内容长度。目前,它看起来像这样:

responseBody rsp  $$+- sink

我想改成这样:

((newResumableSource (yield content-len)) >> (responseBody rsp))  $$+- sink

将初始消息添加到 ResumableSource 之前的一个好方法似乎是使用 conduit 生成初始消息然后成为传递。在这里,我借用了 map conduit 的代码来创建这样一个 conduit:

passThruWInit :: Monad m => BS.ByteString -> C.Conduit BS.ByteString m BS.ByteString
passThruWInit initMsg = do
  C.yield initMsg -- generate initial message first
  C.awaitForever $ C.yield -- now pass-through conduit for all messages

现在,我们更新 responseBody rsp $$+- sink 代码以适应它:

responseBody rsp $=+ passThruWInit someInitMsg $$+- sink

最终结果是首先生成 someInitMsg,然后 responseBody 内容流过。这样,我们就可以将内容长度和其他元数据添加到可恢复的 HTTP 响应正文中。