Haskell 捕获EitherT中的异常并保留底层monad转换器

Haskell 捕获EitherT中的异常并保留底层monad转换器,haskell,Haskell,基本上,我想了解如何进行异常处理。我将异常包与MonadThrow/MonadCatch一起使用,这对我来说没有多大意义。我把它分解成了我能找到的最小的例子。我理解这是相当做作的 首先,我定义了一个任意的异常: data ReadLineException = ReadLineException deriving (Show,Typeable) instance Exception ReadLineException 然后有一个函数,它要求用户输入一个数字,并使用MonadWriter跟踪所发

基本上,我想了解如何进行异常处理。我将
异常
包与
MonadThrow
/
MonadCatch
一起使用,这对我来说没有多大意义。我把它分解成了我能找到的最小的例子。我理解这是相当做作的

首先,我定义了一个任意的
异常

data ReadLineException = ReadLineException deriving (Show,Typeable)
instance Exception ReadLineException
然后有一个函数,它要求用户输入一个数字,并使用MonadWriter跟踪所发生的事情。如果没有输入一个数字,它将使用
Control.Monad.Catch
中的
throwM
抛出异常

ioTell :: (MonadWriter [String] m, MonadIO m, MonadThrow m) => m Int
ioTell = do
    tell ["Some opening string"]
    liftIO $ print "Enter a number: "
    x <- liftIO $ getLine
    let mx = maybeRead x :: Maybe Int
    case mx of Nothing -> do
                    tell ["Invalid entry was chosen: " ++ show x]
                    throwM ReadLineException
               (Just a) -> do
                    tell ["Number chosen was " ++ show a]
                    return a
对于常规类型,我定义了一个带有转义函数的转换器堆栈:

type MyExcept a = EitherT SomeException (WriterT [String] IO) a

runMyExcept :: MyExcept a -> IO (Either SomeException a,[String])
runMyExcept me = runWriterT $ runEitherT me
最后,我连续两次运行
ioTell
,收集两个编写器的输出并合并它们(在我的实际用例中,这更加复杂,因为我使用
async
wait

经过反复试验,我发现在运行
runMyExcept
之前,可以通过运行
eitherCatch
来处理错误。这样,在尝试对异常进行模式匹配之前,将异常抛出到
构造函数中(现在看起来很明显,尽管无法推断程序将完全从类型中逃逸):

第二个问题:这似乎也会强制处理异常。假设我希望整个计算失败,就在一次计算失败的那一刻。我希望异常会通过调用堆栈向上扩散,并在链的任何地方被捕获

假设我能找到第一个问题的解决方案,我想我唯一的选择就是在值一进来就告诉你,然后使用
来阻止剩余的计算继续进行:

testM :: MyExcept Int
testM = do
    (ex,w1) <- liftIO . runMyExcept $ eitherCatch ioTell
    (ey,w2) <- liftIO . runMyExcept $ eitherCatch ioTell
    tell $ w1 ++ w2
    x <- hoistEither ex
    y <- hoistEither ey
    tell ["Combined value is " ++ show (x + y)]
    return (x + y)
testM::MyExcept Int
testM=do

(例如,w1)我不确定
throwM
EitherT
一起是否能满足您的需要,因为例如,评估
runEitherT(throwM ReadLineException::EitherT ReadLineException())
会导致错误,而不是
左ReadLineException
值。。。
EitherT
MonadThrow
实例的文档明确提到它“[t]hrrows异常到基本monad中。”
testM :: MyExcept Int
testM = eitherCatch $ do
    (ex,w1) <- liftIO $ runMyExcept ioTell
    let x = case ex of (Left _) -> 10
                       (Right a) -> a
    (ey,w2) <- liftIO $ runMyExcept ioTell
    let y = case ey of (Left _) -> 10
                       (Right a) -> a  
    tell $ w1 ++ w2 ++  ["Combined value is " ++ show (x + y)]
    return (x + y)
"Enter a number: "
2
"Enter a number: "
x
(Left ReadLineException,[])
testM :: MyExcept Int
testM = do
    (ex,w1) <- liftIO . runMyExcept $ eitherCatch ioTell
    let x = case ex of (Left _) -> 10
                       (Right a) -> a
    (ey,w2) <- liftIO . runMyExcept $ eitherCatch ioTell
    let y = case ey of (Left _) -> 10
                       (Right a) -> a  
    tell $ w1 ++ w2 ++  ["Combined value is " ++ show (x + y)]
    return (x + y)
"Enter a number: "
1
"Enter a number: "
x
(Right 11,["Some opening string","Number chosen was 1","Combined value is 11"])
testM :: MyExcept Int
testM = do
    (ex,w1) <- liftIO . runMyExcept $ eitherCatch ioTell
    (ey,w2) <- liftIO . runMyExcept $ eitherCatch ioTell
    tell $ w1 ++ w2
    x <- hoistEither ex
    y <- hoistEither ey
    tell ["Combined value is " ++ show (x + y)]
    return (x + y)