failable: A 'Failable' error monad class to unify failure across monads that can fail

[ bsd3, control, exceptions, library, monad ] [ Propose Tags ]

This library contains a Failable error monad class to unify failure across monads and transformers most commonly used to implement pipelines that can fail and does so in a simple nonsense way by providing the means of signaling a computation "failure" while striving to keep the failure behaviour consistent with the actual definition of the monad/transformer. Please refer to the README file for a more elaborate description and some examples.

[Skip to Readme]


[Index] [Quick Jump]


Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees


  • No Candidates
Versions [RSS],,,,,,,,,,,,
Change log
Dependencies base (>=4.8 && <5), mtl (>=2.2 && <2.3), transformers (>=0.4.2 && <0.6) [details]
License BSD-3-Clause
Copyright 2019 Erick Gonzalez
Author Erick Gonzalez
Category control, exceptions, monad
Bug tracker
Source repo head: git clone
Uploaded by erick at 2020-01-24T11:40:01Z
Reverse Dependencies 3 direct, 0 indirect [details]
Downloads 5795 total (33 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs available [build log]
Last success reported on 2020-01-24 [all 1 reports]

Readme for failable-

[back to package description]


Yet another "error" handling monad (class)

This library provides a 'Failable' error monad class to unify failure across monads and transformers most commonly used to implement pipelines that can fail.

But.. don't we have 'MonadFail', 'MonadThrow', 'MonadError',.. and the true haskeller should be using 'Alternative' anyway!

I am sure a lot of ink has been spilled in forums and around water coolers all around the world, debating the merits and fallacies of one approach or the other. The reason for this package is not to participate in this discussion but rather to provide a simple no nonsense means of signaling a computation "failure" in those monads that provide the inherent means to do so, and to do it in a consistent manner


data FooError = NotImplemented deriving (Typeable, Show)

instance Exception FooError

foo :: (Failable m) => m Int
foo = failure NotImplemented

Now, if one called foo in a Maybe monad:

> foo :: Maybe Int
> Nothing

the failure is then conveyed by returning Nothing as per definition of the Maybe monad. Now in the case of the Either SomeException monad:

> foo :: Either SomeException Int
> Left NotImplemented

but what if we are working in the IO monad?

> foo :: IO Int
> * * * Exception: NotImplemented

In this case, the failure can only be conveyed by throwing an IO exception.

Now, the point where Failable diverges from say MonadThrow for example is when it comes to monad transformers. For example:

> runMaybeT foo :: IO (Maybe Int)

Would throw an Exception: NotImplemented if it was implemented in a MonadThrow context. Since the reason d'etre for the runMaybeT is to provide the underlying monad (transformer) with Maybe like behaviour, i.e. have Nothing be returned in case of aborting the Maybe pipeline so to speak, then throwing an exception defeats IMHO the purpose of using MaybeT in the first place. So, in the case of Failable:

> runMaybeT foo :: IO (Maybe Int)
> Nothing

And the same thing applies to runExceptT etc.

The IO problem

One of the most common complaints about error monads is that they erroneously give the impression that if the user deals with the returned failed condition (i.e. Nothing or Left <SomeError> for Maybe(MaybeT) or Either(ExceptT) respectively) the job is done and the code is now "safe", when in reality all one has done is opened up an additional error "path" on top of IO exceptions. Regarldess of one's position on IO exceptions, truth is they are not going to go away.. probably ever. So one has to find a way to live with them in the best possible manner. To this effect, this library offers a utility function failableIO. This function can be used if the Failable monad is also an instance of MonadIO and it lifts an IO operation into the monad but in the event of an IO error, it returns this as a failure in the right context. So for example:

foo :: (Failable m, MonadIO m) => m ()
foo = do
  failableIO $ do
    txt <- readFile "foo.txt"
    putStrLn txt
> runExceptT foo
> Left foo.txt: openFile: does not exist (No such file or directory)

> runMaybeT foo
> Nothing

but if ran directly on IO:

> foo
> *** Exception: foo.txt: openFile: does not exist (No such file or directory)

IMHO this is an improvement from having foo fail with an IO exception or a failure value depending on the context.