1

What is a good way to wrap a conduit in ExceptT? The approach should stop the processing when there is an error, and extract the error message. Here is a toy code without error-handling - it just silently stops:

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 ()

How could we change the type signature of sink to something like below?

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

In case of Left, it will be nice to break out of the pipeline, and return the error message to top. I read this 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 to conduit - it seems EitherT suggested in the approach has now been subsumed by ExceptT.

Community
  • 1
  • 1
Sal
  • 4,312
  • 1
  • 17
  • 26

1 Answers1

1

A useful signature to remember is:

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

and with that in mind, this code type checks:

{-# 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 ()

This isn't exactly a sink, but it should illustrate how to do things.

The typing on line (***) goes like this:

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 ()

So the use of return here sets things up so we can apply the ExceptT constructor.

Then when you call bind on an ExceptT ... value, you trigger the error checking code that ExceptT provides. Thus, if inp was a Left an error will be raised.

Update

Here is a version with 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 ()
ErikR
  • 51,541
  • 9
  • 73
  • 124
  • Thank you. I am familiar with exceptT but still can't figure out how to have a signature like `ExceptT Err (Consumer ByteString m ()) ()`. Not sure if that is good way to go either because I am trying to do the error processing at top of pipeline level - something like this ` $$ ` which if in `Left` state at any point should stop and return `Left` error. In case of `Pipes`, it is `ExceptT e (Pipe a b m) r` as noted in the post I linked in my question. – Sal Jun 18 '16 at 14:06
  • Answer updated with `Sink`. Actually I don't think you can create `ExceptT Err (Consumer ...)` because Consumer is universally qualified over one of its type parameters. `Pipe` is not defined like that - it is closer to `Sink`. So I think `ExceptT ... Sink ...` is what you're looking for – ErikR Jun 18 '16 at 14:54
  • Yep, I figured the same. Thanks so much for help. – Sal Jun 18 '16 at 15:06