Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

mapMonadTrans :: MonadTrans xT => (m a -> n b) -> xT m a -> xT n b

The problem is this. I have:

f :: MonadIO m => ReaderT FooBar m Answer;
f = (liftIO getArgs) >>= ...

I need to run this with modified arguments. However, since m is unknown, I cannot simply use

mapReaderT (withArgs args) :: ReaderT r IO b -> ReaderT r IO b

since I need somehow to transform (withArgs args) into m for all m.

One possibility I found is to define my own withArgs, thus:

import System.Environment (setArgs, freeArgv);
withArgv new_args act = do {
  pName <- liftIO System.Environment.getProgName;
  existing_args <- liftIO System.Environment.getArgs;
  bracket (liftIO $ setArgs new_args)
          (\argv -> do {
                      _ <- liftIO $ setArgs (pName:existing_args);
                      liftIO $ freeArgv argv;
                    })
          (const act);
};

withArgs xs act = do {
  p <- liftIO System.Environment.getProgName;
  withArgv (p:xs) act;
};

However, this is a kludge, and specific to one function -- I would need to re-write every withX :: X -> IO a -> IO a, e.g. Control.Exception.handle

What, if any, is a better way to do this?

Edit: In the case of handle, I found Control.Monad.CatchIO. In the other case, I used yet another, briefer kludge (not worth posting) to avoid the kludge above. Still seeking a better solution!

like image 774
M Farkas-Dyck Avatar asked Jul 06 '11 18:07

M Farkas-Dyck


1 Answers

Part of what you are looking for is a hoisting of a monad homomorphism into a monad transformer.

class MonadHoist t where
    hoist :: (Monad m, Monad n) => (forall a. m a -> n a) -> t m a -> t n a

    t :: Monad m => t Identity a -> t m a
    t = hoist (return . runIdentity)

That is to say, given a monad homomorphism f from m to n, you can obtain a monad homomorphism from t m to t n using hoist.

A monad homomorphism is slightly stronger than the types above enforce, namely it is responsible for preserving the monad laws.

f . return = return
f . fmap g = fmap g . f
f . join = join . f . fmap f
         = join . fmap f . f -- by the second law
         = (>>= f) . f       -- >>= in terms of join

Notice the quantifier that I snuck in the type of hoist, MonadHoist turns out to need that flexibility for almost all instances! (Reader happens to be the one case where it doesn't. Try to write MaybeT without it.)

Monad transformers can, in general, instantiate this class. For instance:

instance MonadHoist (StateT s) where
    hoist f (StateT m) = StateT (f . m)

instance MonadHoist (ReaderT e) where
    hoist f (ReaderT m) = ReaderT (f . m)

instance MonadHoist MaybeT where
    hoist f (MaybeT m) = MaybeT (f m)

We don't currently provide it in transformers or mtl package because it would require a Rank2Type, but it is pretty straightforward to implement.

If there is enough demand for it, I'll happily package it up in a monad-extras package.

Now, I said part, because while this answers the question given by the type in the topic of your post, it doesn't address the need reflected by the bulk of the text associated with your question!

For that, you probably want to follow luqui's advice. =)

like image 200
Edward Kmett Avatar answered Oct 27 '22 23:10

Edward Kmett