MonadCatch有什么好处?

时间:2019-07-17 13:12:56

标签: haskell

当我写一些可能失败的函数时:

somefun :: (Monad m, ...) -> ... -> m a
somefun ... =
  ...
  fail "some error"

我可以使用fail失败。但是我也可以重写此函数以使用MonadThrow,所以:

somefun :: (MonadThrow m, ...) -> ... -> m a
somefun ... =
  ...
  throwM "Some error"

因此,今天我们得到了MonadFail,我们也有了Monadfail,从另一个角度来看,我可能会因为throwM而失败。在LTS-11.7中编写此类功能的正确方法是什么? throwMfailfail的好处是什么(因为有些库使用一种方法,而另一种使用另一种方法)?

编辑: 另外,当我看到this时,我听不懂-这是暂时的解决方法,但是在以后的版本中,Monad将从collapse中完全删除吗?

2 个答案:

答案 0 :(得分:1)

fail是用于处理do-notation中模式匹配失败的 handler ,而不是 signals 其他函数要处理的错误的。

摘自MonadFail的文档:

  

当一个值用do-notation绑定时,<-左侧的模式可能不匹配。在这种情况下,此类提供了恢复功能。

答案 1 :(得分:1)

有很多失败的方法,但最终所有失败都被转换为三类:

  • 纯故障处理,类似于MaybeEither单子。这是处理故障的最佳方法,但并非总是可能的
  • 例外。这些可以从纯代码中抛出,但是您实际上应该始终避免这样做。因此,如果您需要处理异常,请停留在IO monad之类的地方。
  • 异步异常。这些可以随时弹出。永远不要从中回收它们,通常在极少数情况下应该使用它们。

这里有一些失败的方法,应该避免:

  • undefined-评估时转换为运行时异常。最糟糕的失败方法,仅作为对某些现有函数(不予评估)的自变量进行辩解,例如。 sizeOfalignment等。这类功能应改为使用Proxy编写,但这是正交的。
  • error-还转换为运行时异常。仅应在不可能发生的不可能的情况下使用。
  • throw-与error相同,但是允许抛出特定的异常。还应避免,由于懒惰,可能会在您最不期望的地方对其进行评估。
  • fail-对于大多数monad,实现是抛出error(默认实现)。正如@chepner指出的那样,它是为模式匹配失败而设计的,不应真正使用。尽管如此,它仍然很流行,尤其是在解析中。

应避免上述所有情况,因为它们的使用会导致纯代码导致运行时异常。

失败的正确方法:

  • MaybeEitherValidation等完全失败。
  • throwIO-在MonadIO时抛出异常的正确方法
  • throwSTM-如果您在STM中,则抛出异常的正确方法。
  • throwM-具有适当的失败实现,具体取决于具体的Monad。换句话说,它推迟了如何使函数用户失败的决定,取决于单子,这可能是纯粹的,也可能是纯粹的。

开头,让我们开始讨论实际的问题。

这是fail为何不好的一个很好的例子:

λ> let unsafeDiv x y = if y == 0 then fail "Division by zero" else pure (x `div` y)
λ> 5 `unsafeDiv` 0 :: Maybe Int
Nothing
λ> 5 `unsafeDiv` 0 :: Either String Int
*** Exception: Division by zero
λ> 5 `unsafeDiv` 0 :: IO Int
*** Exception: user error (Division by zero)

STM是另一个示例,其中fail确实很糟糕,因为它会导致调用默认实现:errorWithoutStackTrace :: [Char] -> a。 (请参阅throwSTM,了解它为什么不好)

因此,fail不仅会获得不同的异常,还会导致错误的行为。

另一方面,我们有MonadThrow

λ> let safeDiv x y = if y == 0 then throwM DivideByZero else pure (x `div` y)
λ> 5 `safeDiv` 0 :: Maybe Int
Nothing
λ> 5 `safeDiv` 0 :: Either SomeException Int
Left divide by zero
λ> 5 `safeDiv` 0 :: IO Int
*** Exception: divide by zero

只要monad支持其传播,我们将总是得到与抛出的异常相同的异常。结果,我们总是可以捕获所引发的异常。它保证了顺序,因此不会因懒惰而逃脱。

我认为,对您问题的最正确答案是使用特定于您所使用的monad的故障方法,但是如果您不提前知道确切的monad,并希望让用户使用选择如何失败的功能,请尝试throwM

在一个相关的主题上,我建议不要使用MonadCatch,而应使用unliftiosafe-exceptions之类的东西。查看有关异常处理here的更多信息。