haskell / mtl

The Monad Transformer Library
http://www.haskell.org/haskellwiki/Monad_Transformers
Other
362 stars 63 forks source link

Suggested addition tryError (originally tryExceptT) #60

Closed ddssff closed 5 years ago

ddssff commented 5 years ago

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
chessai commented 5 years ago

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.

ddssff commented 5 years ago

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
chessai commented 5 years ago

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.

Lysxia commented 5 years ago

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.

ddssff commented 5 years ago

Ah, that would be

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

This seems like a good idea to me.

ddssff commented 5 years ago

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
ddssff commented 5 years ago

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
ddssff commented 5 years ago

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
chessai commented 5 years ago

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

ddssff commented 5 years ago

Yes, I will do this.