【问题标题】:Haskell: Why do the Maybe and Either types behave differently when used as Monads?Haskell:为什么 Maybe 和 Either 类型在用作 Monad 时表现不同?
【发布时间】:2010-09-25 21:07:18
【问题描述】:

我正试图了解 Haskell 中的错误处理。我找到了文章“8 ways to report errors in Haskell”,但我很困惑为什么 Maybe 和 Either 的行为不同。

例如:

import Control.Monad.Error

myDiv :: (Monad m) => Float -> Float -> m Float
myDiv x 0 = fail "My divison by zero"
myDiv x y = return (x / y)

testMyDiv1 :: Float -> Float -> String
testMyDiv1 x y =
    case myDiv x y of
        Left e  -> e
        Right r -> show r

testMyDiv2 :: Float -> Float -> String
testMyDiv2 x y =
    case myDiv x y of
        Nothing -> "An error"
        Just r  -> show r

调用testMyDiv2 1 0 得到"An error" 的结果,但调用testMyDiv1 1 0 得到:

"*** Exception: My divison by zero

(请注意缺少右引号,表明这不是字符串而是异常)。

什么给了?

【问题讨论】:

  • 我使用 mtl-1.1.0.2 为 testMyDiv1 1 0 得到 "My division by zero"。你用的是哪个Control.Monad.Error
  • 正如 sepp2k 所说,这取决于EitherMaybeinstance Monad 声明。理想情况下,您应该使用最新基础中的instance Monad (Either String)mtlmonads-{fd,tf} 中的一个。
  • 我注意到我收到很多“您正在使用旧包 `base' 版本 3.x。未来的 GHC 版本将不支持基本版本 3.x。您应该更新您的代码以使用新的基本版本 4.x。”错误。所以也许我的 ghc 安装已经过时了?阴谋集团更新不适用任何东西。我正在考虑删除我的安装并重新开始。
  • base 不会被 cabal-install 更新,因为 GHC 使用 base 你必须同时更新两者。我建议你使用 GHC 6.12.x 或等待 GHC 7.0。

标签: haskell error-handling monads


【解决方案1】:

简短的回答是,Haskell 中的 Monad 类将 fail 操作添加到 monads 的原始数学思想中,这使得如何将 Either 类型变成(Haskell)Monad 有点争议,因为有有很多方法可以做到这一点。

有几种不同的实现方式可以做不同的事情。我知道的 3 种基本方法是:

  • fail = Left。这似乎是大多数人所期望的,但实际上不能在严格的 Haskell 98 中完成。该实例必须声明为 instance Monad (Either String),这在 H98 下是不合法的,因为它提到了 @ 之一的特定类型987654325@s 参数(在 GHC 中,FlexibleInstances 扩展会导致编译器接受它)。
  • 忽略fail,使用只调用error的默认实现。这就是您的示例中发生的情况。此版本的优点是兼容 H98,但缺点是对用户来说相当令人惊讶(惊喜出现在运行时)。
  • fail 实现调用一些其他类将字符串转换为任何类型。这是在 MTL 的 Control.Monad.Error 模块中完成的,该模块声明了 instance Error e => Monad (Either e)。在这个实现中,fail msg = Left (strMsg msg)。这又是合法的 H98,并且偶尔也会让用户感到惊讶,因为它引入了另一种类型类。不过,与上一个示例相比,惊喜出现在编译时。

【讨论】:

  • 关于上述 ML 讨论的另一个提议是 fail msg = Left (error msg),但由于很多原因,这很丑。
【解决方案2】:

我猜你正在使用monads-fd

$ ghci t.hs -hide-package mtl
*Main Data.List> testMyDiv1 1 0
"*** Exception: My divison by zero
*Main Data.List> :i Either
...
instance Monad (Either e) -- Defined in Control.Monad.Trans.Error
...

查看transformers 包,这是monads-fd 获取实例的地方,我们看到:

instance Monad (Either e) where
    return        = Right
    Left  l >>= _ = Left l
    Right r >>= k = k r

所以,没有任何关于“失败”的定义。一般来说,不鼓励使用 fail,因为它并不总是能保证在 monad 中完全失败(许多人希望看到 fail 从 Monad 类中删除)。

编辑:我应该补充一点,当然不清楚 fail 是否打算保留为默认的 error 调用。 ping haskell-cafe 或维护者可能是值得的。

EDIT2:mtl 实例一直是moved to base,这一举措包括删除fail = Left 的定义并讨论为何做出该决定。据推测,他们希望人们在 monad 失败时更多地使用 ErrorT,因此将 fail 保留用于更灾难性的情况,例如错误的模式匹配(例如:Just x <- e where e -->* m Nothing)。

【讨论】:

  • 如果依赖于我,monad 类应该仅限于 (>>=)。
  • 你对return有什么看法? ;-)
  • @kmm 实际上,数学定义中不包含fail,但它是为了提供简洁的“do”语法而添加的,因此是必要的。
  • @sepp2k 参见真实世界haskell中do-blocks的脱糖章节。 fail 被调用,如果模式匹配在 do-bind 中失败。
  • @TomMD:实际上不是。带有 lambda 的版本将引发异常,do 将使用 fail 并因此返回 Left(当然,除非您使用未定义失败的错误实例版本,在这种情况下它是相同的 - 但只是巧合)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-04
  • 2020-11-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-15
相关资源
最近更新 更多