Haskell: Why do the Maybe and Either types behave differently when used as Monads?
I'm trying to get my head around error handling in Haskell. I've found the article "8 ways to report errors in Haskell" but I'm confused as to why Maybe and Either behave differently.
For example:
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
Calling testMyDiv2 1 0
gives a result of "An error"
, but calling testMyDiv1 1 0
gives:
"*** Exception: My divison by zero
(Note the lack of closing quote, indicating this isn't a string but an exception).
What gives?
The short answer is that the Monad class in Haskell adds the fail
operation to the original mathematical idea of monads, which makes it somewhat controversial how to make the Either type into a (Haskell) Monad
, because there are many ways to do it.
There are several implementations floating around that do different things. The 3 basic approaches that I'm aware of are:
fail = Left
. This seems to be what most people expect, but it actually can't be done in strict Haskell 98. The instance would have to be declared asinstance Monad (Either String)
, which is not legal under H98 because it mentions a particular type for one ofEither
s parameters (in GHC, the FlexibleInstances extension would cause the compiler to accept it).- Ignore
fail
, using the default implementation which just callserror
. This is what's happening in your example. This version has the advantage of being H98 compliant, but the disadvantage of being rather surprising to the user (with the surprise coming at runtime). - The
fail
implementation calls some other class to convert a String into whatever type. This is done in MTL'sControl.Monad.Error
module, which declaresinstance Error e => Monad (Either e)
. In this implementation,fail msg = Left (strMsg msg)
. This one is again legal H98, and again occasionally surprising to users because it introduces another type class. In contrast to the last example though, the surprise comes at compile time.
I'm guessing you're using 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
...
Looking in the transformers package, which is where monads-fd
gets the instance, we see:
instance Monad (Either e) where
return = Right
Left l >>= _ = Left l
Right r >>= k = k r
So, no definition for Fail what-so-ever. In general, fail
is discouraged as it isn't always guaranteed to fail cleanly in a monad (many people would like to see fail
removed from the Monad class).
EDIT: I should add that it certainly isn't clear fail
was intentioned to be left as the default error
call. A ping to haskell-cafe or the maintainer might be worth while.
EDIT2: The mtl
instance has been moved to base, this move includes removing the definition of fail = Left
and discussion as to why that decision was made. Presumably, they want people to use ErrorT more when monads fail, thus reserving fail
for something more catastrophic situations like bad pattern matches (ex: Just x <- e
where e -->* m Nothing
).
精彩评论