Correct usage of MonadError, ErrorT?

Hi, I've got some functions in MonadError with different Error types. I would like to map errors of one Error type onto the other Error type. It seems that the only facility for doing this is mapErrorT, but that seems to force me to work in ErrorT rather than any old instance of MonadError. Am I barking up the wrong tree trying to keep the functions generalised over MonadError? Is there some other paradigm I should use for passing errors across system layers in haskell? I would like to do something like the following, but I don't know how to achieve the 'mapError errMap' part of g. instance Error Int where strMsg s = 0 f :: (MonadError Int m, MonadIO m) => Int -> m String f 0 = do liftIO $ putStrLn "The int is 0, that'll be an error." throwError (-27) f i = do liftIO $ putStrLn ("The int is " ++ show i ++ ", we'll do the division.") return $ show (100 `div` i) g :: (MonadError String m, MonadIO m) => Int -> m String g i = do liftIO $ putStrLn ("Will try to divide by " ++ show i) mapError errMap (f i) h :: (MonadIO m) => Int -> m String h i = (runErrorT (g i)) >>= either (\e -> return ("had an error: " ++ e)) return Thanks Daniel

On Tue, Jan 31, 2006 at 03:00:41PM +1300, Daniel wrote:
I've got some functions in MonadError with different Error types. I would like to map errors of one Error type onto the other Error type.
It seems that the only facility for doing this is mapErrorT, but that seems to force me to work in ErrorT rather than any old instance of MonadError.
What type would your mapError have? The first idea that comes to mind is mapError :: (MonadError e1 m1, MonadError e2 m2) => (e1 -> e2) -> m1 a -> m2 a The problem here is that m1 and m2 have no relation--m1 could be IO and m2 (Either e2)! Not surprisingly, we can't implement that. So what relation do you want between m1 and m2? The only one I can think of is that m1 and m2 are ErrorT transforms of the same inner monad. In this case mapErrorT fits the bill, though it would probably be more convenient to define your own version that maps only the error: mapErrorTE :: (e1 -> e2) -> ErrorT e1 m a -> ErrorT e2 m a Do you have an example where a more generic mapError would make sense? Andrew

On Tuesday 31 January 2006 16:32, Andrew Pimlott wrote:
On Tue, Jan 31, 2006 at 03:00:41PM +1300, Daniel wrote:
I've got some functions in MonadError with different Error types. I would like to map errors of one Error type onto the other Error type.
It seems that the only facility for doing this is mapErrorT, but that seems to force me to work in ErrorT rather than any old instance of MonadError.
What type would your mapError have? The first idea that comes to mind is
mapError :: (MonadError e1 m1, MonadError e2 m2) => (e1 -> e2) -> m1 a -> m2 a
The problem here is that m1 and m2 have no relation--m1 could be IO and m2 (Either e2)! Not surprisingly, we can't implement that.
Yeah, I expected that would be difficult. Is it actually impossible or does it just result in an explosion of code in the implementation, needing a clause to map each instance of MonadError to each other instance on MonadError? Not that I'm suggesting that solution, it's just that I'm new to this stuff and don't immediately see why it's impossible (as opposed to impractical).
So what relation do you want between m1 and m2? The only one I can think of is that m1 and m2 are ErrorT transforms of the same inner monad.
Yep. That's what I was thinking of.
In this case mapErrorT fits the bill, though it would probably be more convenient to define your own version that maps only the error:
mapErrorTE :: (e1 -> e2) -> ErrorT e1 m a -> ErrorT e2 m a
Ok, so mapErrorT, or a convenient wrapper, is the right tool for this situation? If so, doesn't using mapErrorT bind the general MonadError class to the specific ErrorT instance? So, g :: (MonadError String m, MonadIO m) => Int -> m String would have to become g :: (MonadIO m) => Int -> ErrorT String (m String) and that change will propagate out through any function which calls g. It feels bad to me to lose that generality. Is that just a hangover from other/weaker languages? Is it good advice for a new haskeller to stick to ErrorT in functions with errors?
Do you have an example where a more generic mapError would make sense?
Where the inner monad is changed as well? No, but I can't say that I've tried very hard. I'm finding that I have to take quite small bites of haskell so my brain doesn't get indigestion :) Cheers Daniel

On 30/01/06, Daniel McAllansmith
On Tuesday 31 January 2006 16:32, Andrew Pimlott wrote:
On Tue, Jan 31, 2006 at 03:00:41PM +1300, Daniel wrote:
I've got some functions in MonadError with different Error types. I would like to map errors of one Error type onto the other Error type.
It seems that the only facility for doing this is mapErrorT, but that seems to force me to work in ErrorT rather than any old instance of MonadError.
What type would your mapError have? The first idea that comes to mind is
mapError :: (MonadError e1 m1, MonadError e2 m2) => (e1 -> e2) -> m1 a -> m2 a
The problem here is that m1 and m2 have no relation--m1 could be IO and m2 (Either e2)! Not surprisingly, we can't implement that.
Yeah, I expected that would be difficult. Is it actually impossible or does it just result in an explosion of code in the implementation, needing a clause to map each instance of MonadError to each other instance on MonadError? Not that I'm suggesting that solution, it's just that I'm new to this stuff and don't immediately see why it's impossible (as opposed to impractical).
Well, it that case mentioned, it would actually be impossible. If we have a function of type: (IOError -> IOError) -> IO a -> Either IOError a there will be a large problem with referential transparency. Barring unsafe hacks in the runtime system, you can't have such a thing.
So what relation do you want between m1 and m2? The only one I can think of is that m1 and m2 are ErrorT transforms of the same inner monad.
Yep. That's what I was thinking of.
In this case mapErrorT fits the bill, though it would probably be more convenient to define your own version that maps only the error:
mapErrorTE :: (e1 -> e2) -> ErrorT e1 m a -> ErrorT e2 m a
Ok, so mapErrorT, or a convenient wrapper, is the right tool for this situation?
If so, doesn't using mapErrorT bind the general MonadError class to the specific ErrorT instance? So, g :: (MonadError String m, MonadIO m) => Int -> m String would have to become g :: (MonadIO m) => Int -> ErrorT String (m String) and that change will propagate out through any function which calls g.
It feels bad to me to lose that generality. Is that just a hangover from other/weaker languages? Is it good advice for a new haskeller to stick to ErrorT in functions with errors?
Well, it may be possible to rig up something more general, but it's not quite so simple. Moving from IO to much of anything else will be tough. You can map between errors of the same type with mapError :: (MonadError e m) => (e -> e) -> (m a -> m a) mapError f x = catchError x (\e -> throwError (f e)) Having no additional information about the structure of m, it's impossible to map between errors of different types. If we had an extractError :: (MonadError e m) => m a -> Either e a, we could do the general mapError simply by extracting the error and rethrowing it in the other monad, (optionally performing some other computation if there's no error) but this function isn't generally implementable. It implies that there's some way to run the monadic computation and get a value. Then again, if you really wanted one, you could create a new typeclass for exactly that situation. - Cale

On Tue, Jan 31, 2006 at 05:07:00PM +1300, Daniel McAllansmith wrote:
On Tuesday 31 January 2006 16:32, Andrew Pimlott wrote:
What type would your mapError have? The first idea that comes to mind is
mapError :: (MonadError e1 m1, MonadError e2 m2) => (e1 -> e2) -> m1 a -> m2 a
The problem here is that m1 and m2 have no relation--m1 could be IO and m2 (Either e2)! Not surprisingly, we can't implement that.
Yeah, I expected that would be difficult. Is it actually impossible or does it just result in an explosion of code in the implementation, needing a clause to map each instance of MonadError to each other instance on MonadError? Not that I'm suggesting that solution, it's just that I'm new to this stuff and don't immediately see why it's impossible (as opposed to impractical).
As Cale said, you can always define your own class that contains some operation you build mapError on top of. It might even prove generally useful. One idea might be to define a version of MonadError where the monad can be paramatrized on any error type class MonadError' m where throwError' :: e -> m e a catchError' :: m e a -> (e -> m e a) -> m e a and see where you get.
Ok, so mapErrorT, or a convenient wrapper, is the right tool for this situation?
If so, doesn't using mapErrorT bind the general MonadError class to the specific ErrorT instance? So, g :: (MonadError String m, MonadIO m) => Int -> m String would have to become g :: (MonadIO m) => Int -> ErrorT String (m String) and that change will propagate out through any function which calls g.
Right. Note that to use the first signature, g must be written so that it works for any (MonadError String m, MonadIO m). But I could define instance MonadError String MyMonad where ... instance MonadIO MyMonad where ... that _only_ supports String errors. You want to call f, which uses Int errors, from g. But MyMonad cannot take an Int error, so you're stuck. I agree that it would be nicer to keep your definition general. It doesn't seem possible using MonadError, but maybe with MonadError' or some other clever idea, you could make it work.
Is it good advice for a new haskeller to stick to ErrorT in functions with errors?
I'm not sure there's any "common wisdom" about using MonadError. I don't get the sense that it's used that much, which seems a bit of a shame to me. Andrew
participants (4)
-
Andrew Pimlott
-
Cale Gibbard
-
Daniel
-
Daniel McAllansmith