Haskell:为什么Maybe和Either类型在用作Monads时表现不同?

时间:2010-09-25 21:07:19

标签: haskell error-handling monads

我正试图了解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

(注意缺少结束语,表明这不是字符串而是例外)。

是什么给出了?

2 个答案:

答案 0 :(得分:15)

简短的回答是Haskell中的Monad类将fail操作添加到monad的原始数学概念中,这使得如何将Either类型变为(Haskell)Monad会引起争议。 ,因为有很多方法可以做到。

有几个实现浮动,执行不同的事情。我所知道的3种基本方法是:

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

答案 1 :(得分:4)

我猜你正在使用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中干净地失败(很多人希望看到从Monad类中删除fail)。

编辑:我应该补充一点,当然不清楚fail是否有意被保留为默认的error电话。 ping到haskell-cafe或维护者可能是值得的。

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