I'm reading a revealing example of using a bind operator:
Just 5 >>= (\ x -> if (x == 0) then fail "zero" else Just (x + 1) )
returns Just 6
.
I'm confused by the behaviour of fail
and its usefulness in the example.
When looking at the code I thought fail "zero"
may have a meaning:
- program never gets to that point
- laziness
- something else.
Then I realised that after a type cohesion, an exception becomes Nothing
(documented here). Still confusing for me that without type enforcement fail
is just an error in program.
Prelude> fail "zero" :: Maybe Int
Nothing
Prelude> fail "abc" :: [Int]
[]
Prelude> fail "zero"
*** Exception: user error (zero)
My question is about usefulness of fail "zero"
in this example.
Is it a proper reading (\ x -> if (x == 0) then fail "zero" else Just (x + 1) )
attempts to be simple case for a -> Maybe a
function?
What prevents us from using (\ x -> if (x == 0) then Nothing else Just (x + 1) )
if we just needed an illustration of a -> Maybe a
?
I found this version below a much easier and shorter way to grasp same example.
Prelude> g x = if (x == 0) then Nothing else Just (x + 1)
Prelude> Just 0 >>= g
Nothing
Prelude> Just 1 >>= g
Just 2
Yes.
Nothing.
To elaborate on Daniel's entirely correct answer,
fail
is a function from theMonadFail
typeclass. It was originally in theMonad
typeclass, but it didn't really belong there, as someMonad
s don't have a conception of failure. For those that do have a conception of failure,fail
has the typeMonadFail m => String -> m a
. That is, for any type that implements theMonadFail
interface, and any typea
,fail
can take a string and produce an instance of that type paramaterized bya
.For
Maybe a
,fail s = Nothing
.For
[a]
,fail s = []
.For
IO a
,fail
raises an exception containing the string.Many people write instances of
MonadFail
for types likeEither String a
, wherefail s = Left s
. The main reason this is not part of the standard library is becauseEither
is a fairly abstract type, and although it's common to treatRight
as success andLeft
as failure, it can be reasonably used as aMonad
with the semantics flipped. (For instance, if you want to try N things, and move on to the next thing if one thing fails, but take the first result, then you need to useLeft
as the success case.)Edit:
Also, when you write some code like
That gets desugared to:
This means that a failed pattern match will throw an exception in
IO a
, but be an empty list in[a]
, and be aNothing
in aMaybe a
.