haskell - MonadCatch 有什么好处?

标签 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 中编写此类函数的正确方法是什么? throwM有什么好处?与 fail (因为有一种方法和另一种方法的库 - 用其他方法)?

编辑:
还有当我看到this我无法理解 - 这是暂时的解决方法,但在 future 的版本中 fail将从 Monad 中完全删除?

最佳答案

失败的方式有很多种,但最终所有的失败都转化为三类:

  • 纯故障处理,类似于 MaybeEither单子(monad)。这是处理故障的最佳方法,但并非总是可行
  • 异常(exception)。这些可以从纯代码中抛出,但您确实应该始终避免这样做。因此,如果您需要处理异常,请留在 IO 之类的地方。单子(monad)。
  • 异步异常。这些可以随时弹出。它们永远不应该被恢复,并且通常应该在极其罕见的情况下使用。

  • 以下是一些失败的方法,应该避免:
  • undefined - 当评估时被转换为运行时异常。最糟糕的失败方式,仅作为一些现有函数的参数而不会被评估,例如。 sizeOf , alignment等。这类函数应该写成 Proxy相反,但这是正交的。
  • error - 也转化为运行时异常。应该只在不可能发生的不可能的情况下使用。
  • throw - 与 error 相同,但允许抛出特定的异常。也应该避免,因为由于懒惰,它可能会在您最不期望的地方进行评估。
  • fail - 对于大多数 monads 实现是抛出 error (默认实现)。正如@chepner 所指出的,它是为模式匹配失败而设计的,不应该真正使用。尽管如此,它仍然很受欢迎,尤其是在解析方面。

  • 应该避免上述所有情况,因为它们的使用会导致纯代码的运行时异常。
    正确的失败方法:
  • Maybe , Either , Validation等完全失败,无一异常(exception)。
  • throwIO - 在 MonadIO 中抛出异常的正确方法
  • throwSTM - 如果您在 STM 中,则正确抛出异常的方法.
  • throwM - 有一个适当的失败实现,它依赖于一个具体的 Monad .换句话说,它将如何失败的决定推迟给函数的用户,这可以是纯的也可以不是,取决于 monad。

  • 结束前言,让我们来看看实际的问题。
    这是一个很好的例子说明为什么fail很糟糕,在 MonadFail 提案实现之前:
    λ> 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 .

    关于haskell - MonadCatch 有什么好处?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57076728/

    相关文章:

    haskell - 从 Get monad 读取位的 Monadic 方式

    optimization - 数据关系的复杂合并

    algorithm - Haskell 中的旋转卡尺

    haskell - Haskell 中 IO Monad 中元组的模式匹配

    haskell - 如何通过 Haskell 中的自引用键删除列表的一部分

    haskell - 应该如何使用forever函数呢?

    date - 有日期的 Haskell 库吗?

    haskell - Haskell 优化器是否利用 memoization 在范围内重复调用函数?

    haskell - 为什么我必须在这里指定类型?

    haskell - 使用 Stack 构建 Haskell 项目时,我可以指定自定义预构建步骤吗?