我正在寻找最常见的方式来做:
x :: IO ((),[SomeException])
x = do
void y
void z
汇总y
和z
可能引发的异常,并将其作为x
类型的一部分返回。
是否有一个着名的monad /教程?
答案 0 :(得分:4)
所以这里的重要问题是augustss
- “如果y抛出异常,a
的价值是什么?”
如果您有a
和b
的默认值,则可以使用try
来捕获异常并使用WriterT
汇总它们:
x :: IO (C, [SomeException])
x = runWriterT $ do
a <- rescue defaultA y
b <- rescue defaultB z
return $ f a b
rescue :: a -> IO a -> WriterT [SomeException] IO a
rescue a m = do
res <- lift $ try m
case res of
Left e -> do
tell [e]
return a
Right a' -> return a'
data A
data B
data C
y :: IO A
y = undefined
defaultA :: A
defaultA = undefined
z :: IO B
z = undefined
defaultB :: B
defaultB = undefined
f :: A -> B -> C
f = undefined
虽然没有默认值,但您无法挽救异常并继续计算。
答案 1 :(得分:4)
如果y
引发异常,那么您永远不会到达z
。同样,如果z
抛出异常,则表示y
未抛出异常。因此,跟踪异常的典型方法是使用ErrorT
跟踪一个 - 抛出的异常 - 。
x :: ErrorT SomeException IO Foo
x = do
a <- y
b <- z
return $ f a b
useX :: IO Quux
useX = do
errOrVal <- runErrorT x
case errOrVal of
Left err -> logError err >> quux1
Right v -> quux2 v
以下是我使用的类型假设:
{-# LANGUAGE EmptyDataDecls #-}
import Control.Monad.Error
data Foo; data Bar; data Baz; data Quux; data SomeException
instance Error SomeException
y :: ErrorT SomeException IO Bar; y = undefined
z :: ErrorT SomeException IO Baz; z = undefined
f :: Bar -> Baz -> Foo; f = undefined
quux1 :: IO Quux; quux1 = undefined
quux2 :: Foo -> IO Quux; quux2 = undefined
logError :: SomeException -> IO (); logError = undefined
答案 2 :(得分:4)
关于异常的一个重要事项是,如果IO a
操作引发异常,则不会得到任何结果a
值。由于monad的绑定操作符(>>=) :: Monad m => m a -> (a -> m b) -> m b
允许以后的操作依赖于先前操作的结果,这意味着一旦失败,我们就无法运行以下操作。
如果您有默认值,则可以使用rampion's approach。但是,从您的示例中,您似乎只关心通过(>>)
运算符对独立操作的排序。你可以从中做出一个monoid,但我认为最简单的方法就是拥有一个运行IO ()
动作列表的函数,并收集列表中的任何异常:
import Control.Exception (SomeException, try)
import Data.Either (lefts)
exceptions :: [IO ()] -> IO [SomeException]
exceptions = fmap lefts . mapM try
但是,您必须使用操作列表而不是do
表示法:
> :{
| exceptions [ putStrLn "foo"
| , throwIO DivideByZero
| , putStrLn "bar"
| , throwIO (IndexOutOfBounds "xyzzy")
| ]
| :}
foo
bar
[divide by zero,array index out of range: xyzzy]