Обмотка трубопровода в 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 было бы неплохо вырваться из конвейера и вернуть сообщение об ошибке наверх. Я прочитал этот сообщение в блоге но еще недостаточно хорошо это понял, чтобы применить его к каналу (который также имеет сложную сигнатуру типа). Я хотел бы применить предложенный подход здесь для канала - кажется, что EitherT, предложенный в подходе, теперь включен в ExceptT.


person Sal    schedule 18.06.2016    source источник


Ответы (1)


Полезная подпись, которую следует запомнить:

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.

Затем, когда вы вызываете bind для значения 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 ()
person ErikR    schedule 18.06.2016
comment
Спасибо. Я знаком с excludeT, но до сих пор не могу понять, как получить такую ​​подпись, как ExceptT Err (Consumer ByteString m ()) (). Не уверен, что это хороший способ, потому что я пытаюсь выполнить обработку ошибок на верхнем уровне конвейера - что-то вроде этого `‹какой-то канал› $$ ‹какой-то канал›`, который, если он находится в состоянии Left, в любой момент должен остановиться и вернуть Left ошибку. В случае Pipes это ExceptT e (Pipe a b m) r, как указано в сообщении, на которое я ссылался в своем вопросе. - person Sal; 18.06.2016
comment
Ответ обновлен с помощью Sink. На самом деле я не думаю, что вы можете создать ExceptT Err (Consumer ...), потому что Consumer универсально квалифицирован по одному из параметров своего типа. Pipe так не определяется - он ближе к Sink. Так что я думаю, что ExceptT ... Sink ... это то, что вам нужно - person ErikR; 18.06.2016
comment
Ага, я так же подумал. Большое спасибо за помощь. - person Sal; 18.06.2016