haskell/mtl

Suggested addition tryError (originally tryExceptT)

ddssff opened this issue · 11 comments

It seems to me that this function might make a useful addition to Control.Monad.Except:

-- | ExceptT analog to the 'try' function.
tryExceptT :: Monad m  => ExceptT e m a -> ExceptT e m (Either e a)
tryExceptT = lift . runExceptT

Can you give an example of where one might find this useful?

ExceptT e m (Either e a) is the same as m (Either e (Either e a)), nesting the Eithers.

Well, its a small thing, I just noticed it when trying to understand the relationship between GHC exceptions, Ed Kmett's exceptions library, and mtl's Control.Monad.Except. All but mtl have try-like functions that expose the Either in the return value. I'm not sure simple examples will be very convincing though.

λ> runExceptT $ tryExceptT (throwError "an error occurred") >>= either (\e -> liftIO (putStrLn (show e)) >> return 2) return
"an error occurred"
Right 2

Since ExceptT has a MonadCatch instance, this is just Control.Monad.Catch.try. However that requires users to depend on the exceptions library. So, I don't think I would be opposed or not opposed to adding this. Maybe someone else could chime in.

Since this is mtl it could be generalized to tryError :: MonadError m => m a -> m (Either e a), and the specialized tryExceptT could be added to transformers. I've found try to be pretty useful, so I'm somewhat surprised mtl doesn't already have it.

Ah, that would be

tryError :: MonadError e m => m a -> m (Either e a)
tryError action = (Right <$> action) `catchError` (return . Left)
gwils commented

This seems like a good idea to me.

Also, a generalization of withExceptT:

withError :: (MonadError e m, MonadError e' m) => (e -> e') -> m a -> m a
withError f action = tryError action >>= either (throwError . f) return

Actually this withError implementation doesn't work, because there is a functional dependency m -> e. This more limited implementation works:

withError :: MonadEror e m => (e -> e) -> m a -> m a
withError f action = tryError action >>= either (throwError . f) return

However, this analogue of mapExceptT works:

mapError :: (MonadError e m, MonadError e' n) => (m (Either e a) -> n (Either e' b)) -> m a -> n b
mapError f action = f (tryError action) >>= liftEither

@ddssff would you like to prepare a PR? I'm currently in favour, and others are too.

Yes, I will do this.