Helmut Grohne
Helmut Grohne

Reputation: 6778

@djinn MonadError e m => m ()

Is it possible to give an expression of type

MonadError e m => m ()

that raises an error, that can be handled with catchError? Note the absence of an Error e requirement.

Under what circumstances can an error created with fail be handled with catchError? What can lead to a fail not being caught by catchError? In what way did these circumstances change with respect to GHC releases? (There is a mention that the behaviour changed in base 4.3.)

The background of this question is a larger piece of code (Igor2 function nomatch) where an error created with fail "some message" passes by catchError and terminates the program, but replacing the fail invocation with throwError undefined results in the error being caught as expected. Of course throwError undefined is an ugly hack and this question aims to understand the background and correct solution.

Upvotes: 1

Views: 236

Answers (1)

shang
shang

Reputation: 24802

There are several separate issues here, so I'll go through them in order.

MonadError e m => m ()

The problem with the above is that if e is not constrained in any way, it is impossible to create a value of type e which you need for throwError. So no, the only valid value for the above type that raises an error would need to include

throwError undefined

which is a cop-out (and not very useful). The Error e constraint lets you leave e abstract by providing a mechanism to construct a value of type e from a string.

Under what circumstances can an error created with fail be handled with catchError? What can lead to a fail not being caught by catchError?

This depends entirely of the monad in question. Different instances of MonadError might have a different implementation for fail so there is no generic answer. Or to put it another way, there are no guarantees that the error triggered by fail can be handled by catchError unless you are using a specific instance of MonadError that makes that promise. For example:

{-# LANGUAGE FlexibleContexts #-}

import Control.Monad.Error

example :: (Error e, MonadError e m) => m String
example = fail "This is a failure" `catchError` const (return "Error was caught")

main = do
    example >>= putStrLn
    either putStrLn putStrLn example

In the first line of main, example uses the MonadError instance of IO and the second line uses the MonadError instance of Either String. fail results in a catchable error in IO but not in Either so the program outputs

Error was caught
*** Exception: This is a failure

However, if we replace example with

example :: (Error e, MonadError e m) => m String
example = throwError (strMsg "This is a failure")
    `catchError` const (return "Error was caught")

Then we get the desired output

Error was caught
Error was caught

And this would work the same for any (valid) instance of MonadError, unlike fail.

In what way did these circumstances change with respect to GHC releases? (There is a mention that the behaviour changed in base 4.3.)

The change in base 4.3 refers to how fail works in the Either monad. In previous versions fail returned a Left value which could be caught with catchError but in 4.3 and after it uses error and triggers an exception that has to be handled in IO (with e.g. catch).

The problem in Igor2 sounds like it uses the Either monad and assumes the old behavior of fail when it should be using throwError (or simply Left if there is no suitable Error instance).

Upvotes: 3

Related Questions