1

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
Evgeny
  • 4,173
  • 2
  • 19
  • 39

2 Answers2

5

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?

Yes.

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?

Nothing.

Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
3

To elaborate on Daniel's entirely correct answer, fail is a function from the MonadFail typeclass. It was originally in the Monad typeclass, but it didn't really belong there, as some Monads don't have a conception of failure. For those that do have a conception of failure, fail has the type MonadFail m => String -> m a. That is, for any type that implements the MonadFail interface, and any type a, fail can take a string and produce an instance of that type paramaterized by a.

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 like Either String a, where fail s = Left s. The main reason this is not part of the standard library is because Either is a fairly abstract type, and although it's common to treat Right as success and Left as failure, it can be reasonably used as a Monad 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 use Left as the success case.)

Edit:

Also, when you write some code like

do 
  Just y <- getMaybe x
  return y

That gets desugared to:

do
  case getMaybe x of
    Just y -> return y
    _      -> fail "Failed pattern match"

This means that a failed pattern match will throw an exception in IO a, but be an empty list in [a], and be a Nothing in a Maybe a.

Izaak Weiss
  • 1,281
  • 9
  • 18
  • Thank you for addition! The [docs](https://hackage.haskell.org/package/base-4.12.0.0/docs/Prelude.html#v:fail) say `fail` _is invoked on pattern-match failure in a do expression._ Is this useful for anything else? Another way of keeping track of failed computations, like `Either`? – Evgeny May 17 '19 at 19:28
  • 2
    Edited post to include details of what "invoked on pattern match failure" means. It's mostly a way of making "failed computations" generic over different kinds of `Monad`s. One of my favorite uses of this is that you can write a lot of code that is generic over MonadFail, and then choose what type of error you want just by changing the types and some basic definitions. – Izaak Weiss May 17 '19 at 19:35
  • Thank you for example @Izaak Weiss and the `MonadFail` workflow comment! – Evgeny May 17 '19 at 19:37