Reputation: 4326
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
.
Upvotes: 1
Views: 124
Reputation: 52029
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 ()
Upvotes: 1