在 ExceptT 中包装管道

Wrapping conduit in ExceptT

ExceptT 中包裹管道的好方法是什么?该方法应在出现错误时停止处理,并提取错误消息。这是一个没有错误处理的玩具代码 - 它只是默默地停止:

import Data.Conduit as C
import Data.ByteString as BS
import Control.Monad
import Control.Monad.IO.Class
import Data.Text as T

-- just a dummy processing to simulate errors
process :: BS.ByteString -> Either (Int,T.Text) BS.ByteString
process inp = if (BS.null inp) then Left $ (1,"Empty input") else Right inp

-- silent processing - stops on error but doesn't tell us what it is
sink :: MonadIO m => Consumer BS.ByteString m ()
sink = do
       bs <- await
       case bs of
        Just val -> do
            let msg = process val
            case msg of  
              Left _ -> return ()
              Right x -> (liftIO $ return x) >> sink
        Nothing -> return ()

我们如何将 sink 的类型签名更改为如下所示?

sink :: MonadIO m => ExceptT e m (Consumer BS.ByteString m ()) 

Left的情况下,打破管道会很好,并且return错误信息到顶部。 我阅读此 blog post but haven't understood it well enough yet to apply it to conduit (which also has complicated type signature). I will like to apply the proposed approach here 以引导 - 该方法中建议的 EitherT 似乎已被 ExceptT 包含。

要记住的一个有用的签名是:

ExceptT :: m (Either e b)  ->  ExceptT e m b

考虑到这一点,此代码类型检查:

{-# LANGUAGE OverloadedStrings #-}

import Control.Monad.Trans.Class
import Control.Monad.Trans.Except

import Data.Conduit as C
import Data.ByteString.Char8 as BS
import Control.Monad
import Control.Monad.IO.Class
import Data.Text as T

-- just a dummy processing to simulate errors
process :: BS.ByteString -> Either (Int,T.Text) BS.ByteString
process inp = if (BS.null inp) then Left $ (1,"Empty input") else Right inp

type Err = (Int,T.Text)

sink' :: MonadIO m => ExceptT Err (ConduitM ByteString Int m) ()
sink' = do  bs <- lift await
            case bs of
              Just inp -> do
                msg <- ExceptT (return $ process inp)   -- ***
                lift $ yield (BS.length msg)
                liftIO $ BS.putStrLn msg
                sink'
              Nothing -> return ()

这不完全是一个接收器,但它应该说明如何做事。

在线 (***) 的输入是这样的:

process inp             :: Either Err ByteString
                            -- (a pure value)
return (process inp)    :: m (Either Err ByteString)
                            -- here m = ConduitM ByteString Int mIO
ExceptT (...)           :: ExceptT Err m ()

所以在这里使用 return 进行设置,这样我们就可以应用 ExceptT 构造函数。

然后,当您对 ExceptT ... 值调用绑定时,您会触发 ExceptT 提供的错误检查代码。因此,如果 inp 是一个 Left 将引发错误。

更新

这里是 Sink 的版本:

sink' :: MonadIO m => ExceptT Err (Sink ByteString m) ()
sink' = do  bs <- lift await
            case bs of
              Just inp -> do
                msg <- ExceptT (return $ process inp)
                liftIO $ BS.putStrLn msg
                sink'
              Nothing -> return ()