Reputation: 6778
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
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