将带有 ExceptT 的源传递给接收器

Passing a source with ExceptT to a sink

给定具有以下类型签名的管道源:

sourceMsg :: MonadIO m => ExceptT Err (ConduitM () ByteString m) ()

怎么传给Data.Conduit.List.mapM_?像下面这样的东西是行不通的,因为输出的类型是 ConduitM a o m (),而不是 ConduitM a o m (Either Err ())

> :t  ($$) (runExceptT $ sourceMsg undefined) (mapM_ undefined)
<interactive>:1:7: Warning:
    Couldn't match type ‘Either Err ()’ with ‘()’
    Expected type: Source m ByteString
      Actual type: ConduitM () ByteString m (Either Err ())
    In the first argument of ‘($$)’, namely
      ‘(runExceptT $ sourceMsg undefined)’
    In the expression:
      ($$) (runExceptT $ sourceMsg undefined) (mapM_ undefined)

我只想在 mapM_ 内打印字节串的长度。

让我们假设您的水槽管道段是 C.mapM_ BS.putStrLn

第一步是解包 ExceptT 值并将其与接收器段进行比较:

runExceptT sourceMsg :: ConduitM () ByteString IO (Either Err ())
C.mapM_ BS.putStrLn  :: ConduitM ByteString a  IO ()

当您使用 Conduit 融合运算符时,您必须选择哪个段将 return 融合表达式的值。两个主要选择是:

a =$= b                -- b returns the value
a `fuseUpstream` b     -- a returns the value

因为我们想看到 Either Err () 值,我们将使用 fuseUpstream:

let f = (runExceptT sourceMsg) `fuseUpstream` (C.mapM_ BS.putStrLn)
    :: ConduitM () c IO (Either Err ())

(将两个段融合在一起的另一个要求是段 而不是 returning 值必须 return ()。在我们的如果 C.mapM_ ... 已经满足了,但通常需要检查。通过使用 const ())

下一步是 运行 融合片段以产生 IO 操作:

runConduit f :: IO (Either Err ())

现在我们可以判断是否有错误。完整解决方案:

{-# LANGUAGE OverloadedStrings #-}

import Control.Monad.Trans.Except
import Data.Conduit as C
import Data.Conduit.List as C
import qualified Data.ByteString.Char8 as BS
import Data.ByteString (ByteString)
import Control.Monad

type Err = (Int, String)

sourceMsg :: ExceptT Err (ConduitM () ByteString IO) ()
sourceMsg = undefined

runSource = do
  do r <- runConduit $ (runExceptT sourceMsg) `fuseUpstream` (C.mapM_ BS.putStrLn)
     case r of
       Left _ -> putStrLn "error"
       Right _ -> putStrLn "no error"