我正试图了解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
(注意缺少结束语,表明这不是字符串而是例外)。
是什么给出了?
答案 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
)。