异常和monad变换器

Emm*_*ery 12 haskell monad-transformers

我正在使用EitherT monad变压器.将它与IO monad相结合,恐怕我会得到一个异常并且它不会被捕获.

事实上,例外只是通过:

import Control.Monad.Trans
import Control.Error
import System.Directory

main = runEitherT testEx >>= print

testEx :: EitherT String IO ()
testEx = lift $ removeFile "non existing filename"
Run Code Online (Sandbox Code Playgroud)

但是EitherT否则完全符合法案以向呼叫者传达错误.所以我想使用它,而不是抛出异常......

try从Control.Exception 看了一下:

try :: Exception e => IO a -> IO (Either e a) 
Run Code Online (Sandbox Code Playgroud)

它看起来正是我想要的,它将适合我的EitherT IO堆栈...(可能有一个添加hoistEither,也许fmapL它开始看起来很冗长)但是一个天真的lift $ try不是类型检查.

我确信这个问题已经解决了好几千次,但我找不到任何描述这个问题的好链接.这应该如何解决?

编辑通过"这应该如何解决",我对惯用解决方案感兴趣,在haskell中处理它的标准方法是什么.从目前为止的答案来看,似乎惯用的方法是抛出异常并将其处理得更高.看起来有点反直觉,有两个控制和返回路径流,但它显然是它的意图.

Mic*_*man 16

我认为EitherT不是正确的做法.你想说的是" IO是副作用,EitherT是异常." 但事实并非如此:IO 总是有可能导致异常,所以你所做的就是为你的API添加一种错误的安全感,并引入两种方式来抛出异常而不是一种异常.此外,不是使用SomeException优先考虑的良好结构,而是IO减少到String丢弃信息.

无论如何,如果你确信这是你想要做的,那就不难了.它看起来像:

eres <- liftIO $ try x
case eres of
    Left e -> throwError $ show (e :: SomeException)
    Right x -> return x
Run Code Online (Sandbox Code Playgroud)

但请注意,这也会吞噬异步异常,这通常不是您想要做的.我认为更好的方法是封闭 - 例外.

  • 我还建议看一下`exceptions`包.不要让你的纯代码直接存在于`Either`中,而是让它存在于`MonadThrow`的任何实例中. (2认同)
  • 唯一的问题是它不会与IO中的代码统一.通过针对类型类编程,您的代码将自动适用于Either和IO,无需任何转换. (2认同)

Cir*_*dec 5

你不想进行lift try计算,那么你就得到了一个Exception e => EitherT a IO (Either e ()).

testEx :: (Exception e, MonadTrans m) => m IO (Either e ())
testEx = lift . try $ fails
Run Code Online (Sandbox Code Playgroud)

您不希望结果中出现错误,您希望将错误集成到EitherT.你想要将try某些东西与你的东西融为一体EitherT

testEx :: (Exception e) => EitherT e IO ()
testEx = EitherT . try $ fails
Run Code Online (Sandbox Code Playgroud)

我们一般会这样做,然后得到你想要的信息.

将尝试与EitherT集成

您可以提取相结合的理念tryEitherT

tryIO :: (Exception e) => IO a -> EitherT e IO a
tryIO = EitherT . try
Run Code Online (Sandbox Code Playgroud)

或者,对于任何潜在MonadIO

tryIO :: (Exception e, MonadIO m) => IO a -> EitherT e m a
tryIO = EitherT . liftIO . try
Run Code Online (Sandbox Code Playgroud)

(tryIO与姓名冲突Control.Error.我无法为此提出另一个名字.)

然后你可以说你愿意抓住任何例外.SomeException将捕获所有异常.如果您只对特定例外感兴趣,请使用其他类型.有关详细信息,请参见Control.Exception.如果你不确定你想要捕获什么,你可能只想抓住IOExceptions; 这是tryIOControl.Error做; 见最后一节.

anyException :: EitherT SomeException m a -> EitherT SomeException m a
anyException = id
Run Code Online (Sandbox Code Playgroud)

您只想保留异常的错误消息

message :: (Show e, Functor m) => EitherT e m a -> EitherT String m a
message = bimapEitherT show id
Run Code Online (Sandbox Code Playgroud)

然后你就可以写了

testEx :: EitherT String IO ()
testEx = message . anyException . tryIO $ fails
Run Code Online (Sandbox Code Playgroud)

使用MonadError集成try

您可以将try某些东西与任何东西集成MonadError,使用MonadErrorMonadIO穿透变压器堆栈.

import Control.Monad.Except

tryIO :: (MonadError e m, MonadIO m, Exception e) => IO a -> m a
tryIO = (>>= either throwError return) . liftIO . try
Run Code Online (Sandbox Code Playgroud)

你可以写testEx在这方面tryIOanyExceptionmessage从上一节

testEx :: EitherT String IO ()
testEx = message . anyException . tryIO $ fails
Run Code Online (Sandbox Code Playgroud)

来自Control.Error的tryIO

tryIO从Control.Error基本上是我们的第一次tryIO,但它只能抓住IOExceptions,而不是任何异常.它实际上被定义为

tryIO :: (MonadIO m) => IO a -> EitherT IOException m a
tryIO = EitherT . liftIO . try
Run Code Online (Sandbox Code Playgroud)

我们可以用它message来写testEx

testEx :: EitherT String IO ()
testEx = message . tryIO $ fails
Run Code Online (Sandbox Code Playgroud)