What do you think of designing API functions to use MonadError to handle errors instead of MonadThrow? I think the former is better because you specify the exact type of the error the API will return.
The problem with using MonadThrow is that the caller can theoretically expect just about any exceptions to be thrown. Whereas with MonadError e, the now-small range of errors that the caller can ex...
IMO, they serve different purposes—MonadThrow and MonadCatch are really about dealing with IO exceptions (though they have some other instances), and I mostly avoid MonadThrow because I usually don’t want to use IO exceptions. I’d much rather deal with an API that provides MonadError constraints than one that provides MonadThrow constraints.
That said, it’s worth noting that MonadMask is a different beast: it’s fundamentally something MonadError cannot provide, because MonadMask allows you to implement a finally operation that runs if any monad in the stack causes an abort. For example, if you have ExceptT e IO, Control.Monad.Catch.finally will execute the action whether you raise an IO exception or call throwError. In that sense, MonadMask is “deep” while MonadError is “shallow.” You need MonadMask if you need reliable recovery from aborts; MonadError just won’t cut it.
It is surprising, because here I'm thinking "why doesn't compiler use one instance for the first let statement, and another for the other let statement"
I mean, I explicitly specified Either MyError as the monad in the second invocation; so GHC should have no trouble, in theory, figuring out the right instance to use?
The problem with using MonadThrow is that the caller can theoretically expect just about any exceptions to be thrown. Whereas with MonadError e, the now-small range of errors that the caller can ex...
You couldn't use it with IO, because IO is bound to IOException (even though it could possibly throw any), while Maybe wouldn't work because it's bound to () (even though you could just ignore error's content and return Nothing)
Think of it this way - MonadError sort of requires m to "keep" original error in case it occurs (catchError is "proof" of that) - this can't be satisfied by Maybe properly
But MonadThrow separates that proof to MonadCatch - so even though Maybe can't have MonadCatch instance, it can have MonadThrow
What do you think of designing API functions to use MonadError to handle errors instead of MonadThrow? I think the former is better because you specify the exact type of the error the API will return.
The
path
library doesn't do this, so I've initiated a discussion there: https://github.com/commercialhaskell/path/issues/149I tend to prefer
MonadError
too, it makes error code/handling more explicitIMO, they serve different purposes—
MonadThrow
andMonadCatch
are really about dealing withIO
exceptions (though they have some other instances), and I mostly avoidMonadThrow
because I usually don’t want to useIO
exceptions. I’d much rather deal with an API that providesMonadError
constraints than one that providesMonadThrow
constraints.That said, it’s worth noting that
MonadMask
is a different beast: it’s fundamentally somethingMonadError
cannot provide, becauseMonadMask
allows you to implement afinally
operation that runs if any monad in the stack causes an abort. For example, if you haveExceptT e IO
,Control.Monad.Catch.finally
will execute the action whether you raise anIO
exception or callthrowError
. In that sense,MonadMask
is “deep” whileMonadError
is “shallow.” You needMonadMask
if you need reliable recovery from aborts;MonadError
just won’t cut it.Why does
MonadError e m
have the functional dependencym -> e
?@Sridhar Ratnakumar From instances it seems like underlying
m
always determinese
Well I'm looking at the Either instance - and I don't see... oh wait!Either e
determininge
Okay this does surprise me at first glance: pasted image
It is surprising, because here I'm thinking "why doesn't compiler use one instance for the first
let
statement, and another for the otherlet
statement"Looks like the type annotation should be in the
_
s, not on the left side.That's not it.
Even this fails:
@Sridhar Ratnakumar https://www.stackage.org/haddock/lts-14.15/mtl-2.2.2/Control-Monad-Error.html#t:MonadError - there's only one instance for
Maybe
(with()
) - butsomeFunc
wants one that usesMyError
(There can only be one because of fundep)
Yea, the question is why is the second invocation not using the Either instance?
Why does the compiler not use two instances in the same function (
main
)?I mean, I explicitly specified
Either MyError
as the monad in the second invocation; so GHC should have no trouble, in theory, figuring out the right instance to use?Error talks about first invocation
Uh, right. I didn't see.
Maybe
was a bad choice.I'm trying to understand the problem Chris points to in the 3rd paragraph of https://github.com/commercialhaskell/path/issues/149#issuecomment-558069993
So if I use a custom error type (
MyError
) withMonadError
- the only instance I use for them
isEither e
?(plus, transformer versions)
You couldn't use it with
IO
, becauseIO
is bound toIOException
(even though it could possibly throw any), whileMaybe
wouldn't work because it's bound to()
(even though you could just ignore error's content and returnNothing
)This works:
IO and
Either e
at the same time.Actually, now that I think about it -
catchError
cannot be implemented properly for polymorphicMaybe
instance, because you lose original errorYou could
Monoid
yourself out though if it's content is not importantYou can have safe
MonadThrow e Maybe
though becausecatch
is inMonadCatch
Think of it this way -
MonadError
sort of requiresm
to "keep" original error in case it occurs (catchError
is "proof" of that) - this can't be satisfied byMaybe
properlyBut
MonadThrow
separates that proof toMonadCatch
- so even thoughMaybe
can't haveMonadCatch
instance, it can haveMonadThrow
Yes, but with MonadThrow|Catch we are dealing with bottoms which "bypass" a strict type system.