哪个monad在Haskell中用于聚合执行一系列语句时可能发生的异常?

时间:2012-03-04 09:35:29

标签: haskell exception-handling error-handling io monads

我正在寻找最常见的方式来做:

x :: IO ((),[SomeException])
x = do
  void y
  void z

汇总yz可能引发的异常,并将其作为x类型的一部分返回。

是否有一个着名的monad /教程?

3 个答案:

答案 0 :(得分:4)

所以这里的重要问题是augustss - “如果y抛出异常,a的价值是什么?”

如果您有ab的默认值,则可以使用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]