views:

259

answers:

2

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?

+2  A: 

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).

TomMD
The monad class should be limited to just (>>=) if it depended on me.
kmm
What do you have against `return`? ;-)
TomMD
@kmm Actually, the mathematical definition does not contains `fail`, but it was added in order to provide the neat `do' syntax'and is thus a necessary evil.
FUZxxl
@FUZxxl: What's the relation between `do` and `fail`? The desugaring of `do` only uses `>>=` and `>>`, no?
sepp2k
I'm getting the same behaviour from both mtl-1.1.0.2 and monads-fd-0.1.0.1. So is this a change in behaviour from 0.1.0.1 to 0.1.0.2 of monads-fd?
Magnus
@sepp2k See chapter desugaring of do-blocks in real world haskell. `fail` is called, if pattern matching fails in a do-bind.
FUZxxl
Magnus: Where is you `instance Monad (Either String)` coming from? I was getting it from `transformers-0.2.2.0`, not directly from `monads-fd`.
TomMD
FUZxxl: That fails to explain how `fail` and `do` are related. A pattern match failure in `do` notation is the same as a pattern match failure using `>>=`. Take the example `Right Nothing >>= (\\((Just x)) -> Right x)` and compare it to `do { (Just x) <- Right Nothing ; Right x }`.
TomMD
@TomMD: Actually it's not. The version with the lambda will throw an exception, the `do` one will use `fail` and thus return `Left` (unless of course you're using the version of the Error instance where fail is not defined, in which case it is the same - but only by coincidence).
sepp2k
Ahh, yes of coarse. What was I thinking. Thank you sepp2k.
TomMD
+3  A: 

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 as instance Monad (Either String), which is not legal under H98 because it mentions a particular type for one of Eithers parameters (in GHC, the FlexibleInstances extension would cause the compiler to accept it).
  • Ignore fail, using the default implementation which just calls error. 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's Control.Monad.Error module, which declares instance 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.
mokus
Another proposal on the above-mentioned ML discussion was `fail msg = Left (error msg)`, but that's ugly for so many reasons.
TomMD