On Tue, Dec 8, 2009 at 11:51 AM, Gregory Crosswhite <gcross@phys.washington.edu> wrote:
Michael,

Although I like the idea of improving the way that failures are handled in Haskell, I am having trouble seeing any reason to use your framework.

If a function is always assumed to succeed given certain pre-conditions, and somewhere along the lines my code discovers that one of these had been violated, then I throw an exception in order to communicate to myself the manner in which I screwed up.

If a function might either succeed or fail for a very specific reason, then I use a Maybe because the caller will know exactly what problem is being signaled by Nothing.

If a function might fail for one of many reasons and the caller might care about learning more about the problem, then I use an Either so that I can report the details of the problem.

In each of the latter two cases, I can already use monads to handle the errors in a relatively convenient manner.  In the first case I usually want the program to die right away and let me know where I screwed up, but if for some reason I wanted to continue even given arbitrary unanticipated problems in a particular computation the I use an exception handler.

Given that all of the scenarios that I encounter are already handled by the functionality the standard libraries, it is not clear to me what your framework actually offers.

I am not writing this to shoot your framework down, but rather to voice my thoughts aloud in order to hear your response to them, since if there is a use scenario that I am missing in which your framework would be ideal then I would be interested in hearing about it.

The failure wiki page addresses exactly why Maybe and Either are inadequate solutions in a number of circumstances. Maybe does not allow any information to be attached; that may be fine when you directly call a function, but what if you call a function, which calls another one, and so on and so forth? You would then have two choices:

* Ultimately just receive a Nothing and not know why.
* At each step of the why, check if it's a Nothing, and then produce some appropriate message to describe it.

If you choose option 2, you're admitting that Maybe was not sufficient for your uses any.  Regarding Either, there are a number of issues:

* There *is* not Monad instance in the base library. Orphan instances lead to major issues, such as not being able to import two modules at the same time because each declares a Monad Either instance.
* With Either, you are limited to a single error type (unless you use existential types).
* There is no easy way to chain together different types of Eithers (see below).

For example, let's say I want to write some code to authenticate a user via OpenID (see the authenticate package). It has to do at least two things:

* Download pages by HTTP
* Parse the resulting HTML page.

I would like to ideally do the following:

authenticate = do
  page <- getPage url
  parsed <- parseHtml page
  checkResult parsed

In other words, easily chain things together, and simply let the error types propogate. If the given functions had these signatures:

getPage :: (MonadFailure HttpException m, MonadIO m) => String -> m String
parseHtml :: Failure ParseHtmlException m => String -> m HtmlTree
checkResult :: Failure AuthenticationException m => HtmlTree -> m Identifier

Then authenticate would simply subsume all these error types, making it explicit what a client of the libraries needs to be aware of. If clients instead want to look at it as a simple success/failure, I would recommend using the attempt package. If they want to deal with each failure type separately, they could use control-monad-exception.

I hope that clears up why this package exists. It was not born of nothing; it is meant to solve real problems in an elegant manner. If you have any questions, please let me know.

Michael