例外和单子变压器 [英] exceptions and monad transformers

查看:161
本文介绍了例外和单子变压器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



事实上,异常只是通过:

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

main = runEitherT testEx>> = print

testEx :: EitherT String IO()
testEx = lift $ removeFilenon existing filename

但是, EitherT 完全适合传达给调用者的错误。所以我想使用它,而不是抛出异常......



我从Control.Exception中查看 try

  try :: Exception e => IO a  - > IO(或者ea)

它看起来正是我想要的,它适合我的EitherT IO堆栈...(可能是添加了 hoistEither 或者 fmapL ,但它开始看起来很冗长)但是一个天真的 lift $ try 不会检测。



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



编辑通过这应该如何解决,我对这种惯用的解决方案感兴趣,在Haskell中处理这个问题的标准方式是什么?从迄今为止的答案来看,惯用的方法是让例外被抛弃,并处理更高层次的例外。看起来有点反直觉有两个控制流和返回路径流,但它显然是它意味着要完成的方式。

解决方案

你不想 lift try 计算,那么你会得到一个 Exception e => (或e())

  testEx ::(Exception e,MonadTrans m )=> m IO(e())
testEx =电梯。尝试$失败

您不希望结果中出现错误,您希望将错误集成到 EitherT 。您希望将 try 与您的 EitherT

  testEx ::(Exception e)=>或者T IO()
testEx = EitherT。尝试$失败

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



用EitherT集成尝试



您可以提取集成 try code> EitherT

  tryIO ::(Exception e)= > IO a  - >或者你可以尝试一下。尝试

或者,对于任何底层 MonadIO

  tryIO ::(Exception e,MonadIO m)=> IO a  - >或者是
tryIO = EitherT。 liftIO。尝试

tryIO 与名称冲突 Control.Error 。我无法想出另一个名字。)



然后你可以说你愿意接受任何例外。 SomeException 会捕获所有异常。如果您只对特定例外感兴趣,请使用其他类型。有关详细信息,请参见 Control.Exception 。如果你不确定你想要捕获什么,你可能只想捕获 IOException s;这就是 Control.Error 中的 tryIO 请参阅上一节。

  anyException :: EitherT SomeException m a  - > EitherT SomeException ma 
anyException = id

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

  message ::(Show e,Functor m)=>或者T e m a  - > EitherT String ma 
message = bimapEitherT show id

然后你可以写成

  testEx :: EitherT String IO()
testEx = message。 anyException。 tryIO $失败



与MonadError集成尝试



您可以使用 MonadError 来集成 try 将某些内容与任何 MonadError $ c>和 MonadIO 穿透变压器堆栈。

  import控制.Monad.Except 

tryIO ::(MonadError em,MonadIO m,Exception e)=> IO a - > m a
tryIO =(>> = throwError return)。 liftIO。试试

您可以按照以下方式编写 testEx 这个 tryIO anyException message / p>

  testEx :: EitherT String IO()
testEx = message。 anyException。 tryio $失败



来自Control.Error的tryIO



Control.Error中的 tryIO 实际上是我们的第一个 tryIO ,除了它仅捕获 IOException s,而不是任何异常。它实际上定义为:

  tryIO ::(MonadIO m)=> IO a  - >或者T IOException m a 
tryIO = EitherT。 liftIO。尝试

我们可以将它与 message 一起用于写 testEx 作为

  testEx :: EitherT String IO()
testEx =消息。 tryIO $失败


I'm using the EitherT monad transformer. Combining it with the IO monad, I'm afraid I would get an exception and it would not be caught.

Indeed the exception just passes through:

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

main = runEitherT testEx >>= print

testEx :: EitherT String IO ()
testEx = lift $ removeFile "non existing filename"

But the EitherT otherwise fits the bill perfectly to convey to callers the error. So I want to use that, not throw exceptions...

I looked at try from Control.Exception:

try :: Exception e => IO a -> IO (Either e a) 

It looks to be exactly what I want, it would fit in my EitherT IO stack... (probably with an added hoistEither and maybe fmapL and it starts looking verbose though) But a naive lift $ try doesn't typecheck.

I'm sure this problem has been solved thousands of times, but I can't find any good link describing this exact issue. How is this supposed to be solved?

EDIT By "how is this supposed to be solved", I was interested in the idiomatic solution, what would be the standard way to handle that in haskell. From the answers so far, it seems the idiomatic way is to let the exceptions be thown and handle them higher-up. Seems like a bit counter-intuitive to have two flows of control and return paths, but it is apparently the way it's meant to be done.

解决方案

You don't want to lift trying the computation, then you'd get an Exception e => EitherT a IO (Either e ()).

testEx :: (Exception e, MonadTrans m) => m IO (Either e ())
testEx = lift . try $ fails

You don't want the error in the result, you want to integrate the error into the EitherT. You want to integrate trying somethign with your EitherT

testEx :: (Exception e) => EitherT e IO ()
testEx = EitherT . try $ fails

We'll do this in general, then get just the message you want.

Integrate try with EitherT

You can extract the idea of integrating try with EitherT

tryIO :: (Exception e) => IO a -> EitherT e IO a
tryIO = EitherT . try

Or, for any underlying MonadIO as

tryIO :: (Exception e, MonadIO m) => IO a -> EitherT e m a
tryIO = EitherT . liftIO . try

(tryIO conflicts with a name from Control.Error. I couldn't come up with another name for this.)

You can then say you are willing to catch any exception. SomeException will catch all exceptions. If you are only interested in specific exceptions, use a different type. See Control.Exception for the details. If you aren't sure what you want to catch, you probably only want to catch IOExceptions; this is what tryIO from Control.Error does; see the last section.

anyException :: EitherT SomeException m a -> EitherT SomeException m a
anyException = id

You only want to keep the error message from the exception

message :: (Show e, Functor m) => EitherT e m a -> EitherT String m a
message = bimapEitherT show id

Then you can write

testEx :: EitherT String IO ()
testEx = message . anyException . tryIO $ fails

Integrate try with MonadError

You can instead integrate trying something with any MonadError, using MonadError and MonadIO to penetrate the transformer stack.

import Control.Monad.Except

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

You can write testEx in terms of this tryIO and anyException and message from the previous section

testEx :: EitherT String IO ()
testEx = message . anyException . tryIO $ fails

tryIO from Control.Error

The tryIO from Control.Error is essentially our first tryIO, except it only catches IOExceptions instead of any exception. It's actually defined as

tryIO :: (MonadIO m) => IO a -> EitherT IOException m a
tryIO = EitherT . liftIO . try

We can use it with message to write testEx as

testEx :: EitherT String IO ()
testEx = message . tryIO $ fails

这篇关于例外和单子变压器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆