当我写一些可能失败的函数时:
somefun :: (Monad m, ...) -> ... -> m a
somefun ... =
...
fail "some error"
我可以使用fail
失败。但是我也可以重写此函数以使用MonadThrow
,所以:
somefun :: (MonadThrow m, ...) -> ... -> m a
somefun ... =
...
throwM "Some error"
因此,今天我们得到了MonadFail
,我们也有了Monad
和fail
,从另一个角度来看,我可能会因为throwM
而失败。在LTS-11.7中编写此类功能的正确方法是什么? throwM
与fail
和fail
的好处是什么(因为有些库使用一种方法,而另一种使用另一种方法)?
编辑:
另外,当我看到this时,我听不懂-这是暂时的解决方法,但是在以后的版本中,Monad
将从collapse
中完全删除吗?
答案 0 :(得分:1)
fail
是用于处理do-notation中模式匹配失败的 handler ,而不是 signals 其他函数要处理的错误的。
摘自MonadFail
的文档:
当一个值用do-notation绑定时,<-左侧的模式可能不匹配。在这种情况下,此类提供了恢复功能。
答案 1 :(得分:1)
有很多失败的方法,但最终所有失败都被转换为三类:
Maybe
或Either
单子。这是处理故障的最佳方法,但并非总是可能的IO
monad之类的地方。这里有一些失败的方法,应该避免:
undefined
-评估时转换为运行时异常。最糟糕的失败方法,仅作为对某些现有函数(不予评估)的自变量进行辩解,例如。 sizeOf
,alignment
等。这类功能应改为使用Proxy
编写,但这是正交的。error
-还转换为运行时异常。仅应在不可能发生的不可能的情况下使用。 throw
-与error
相同,但是允许抛出特定的异常。还应避免,由于懒惰,可能会在您最不期望的地方对其进行评估。fail
-对于大多数monad,实现是抛出error
(默认实现)。正如@chepner指出的那样,它是为模式匹配失败而设计的,不应真正使用。尽管如此,它仍然很流行,尤其是在解析中。应避免上述所有情况,因为它们的使用会导致纯代码导致运行时异常。
失败的正确方法:
Maybe
,Either
,Validation
等完全失败。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
,而应使用unliftio
或safe-exceptions
之类的东西。查看有关异常处理here的更多信息。