Juan Casanova Jaquete
Juan Casanova Jaquete

Reputation: 123

Function in Haskell to map over a MonadTrans?

I have recently decided to start using monad transformations instead of stacking my monads, since it seems it's the right thing to do. I wasn't really stacking many monads before anyway. I get (I think) the idea behind it and the lift function, that, as I understand it, acts as a sort of return for the transformation (puts something from the underlying monad into the transformed monad).

So far so good, but I don't see anything similar to an fmap function for monad transformations. Let me give you an example. Say I have a custom monad, m, and I use a StateT transformation on it, therefore using the type StateT s m a instead of m (State s a).

Now, it so happens that in my monad m, I have a function that transforms the monadic element (in fact it is one of the constructors of the monad, if you need details I can give) while keeping in some sense the underlying values: myFunc :: m a -> m a.

So I'm building a recursive function recFunc :: State s a -> [t] -> m (State s a) that looks similar to something like this:

recFunc :: State s a -> [t] -> m (State s a)
recFunc x [] = return x
recFunc x (t:ts) = myFunc (recFunc x ts)

But if I try to replicate this using monad transformations, I run into problems because I can find no way to plug in myFunc into the mix. It does not matter whether you write the input as State s a or as StateT s Identity a (which would be algebraically more precise?)

recFuncT :: StateT s Identity a -> [t] -> StateT s m a
recFuncT x [] = ???
recFuncT x (t:ts) = ????? where rec = recFuncT x ts

So what I'm looking for is something like the (invented, and don't know how I would implement, if possible) following functions:

transmap :: (MonadTrans t, Monad m) => (forall b. m b -> m b) -> t m a -> t m a
transmap = ???

transreturn :: (MonadTrans t, Monad m) => m (t Identity a) -> t m a
transreturn = ???

I have the feeling I should be able to define these using lift, but I don't see how, to be honest.

If I had them, then I could do this:

recFuncT :: StateT s Identity a -> [t] -> StateT s m a
recFuncT x [] = transreturn (return x)
recFuncT x (t:ts) = transmap myFunc (recFuncT x ts)

Maybe what I really want is something more basic. I want the assumed isomorphism between t m a and m (t Identity a) to be explicit, so I'm looking for functions:

fromTrans :: t m a -> m (t Identity a)
toTrans :: m (t Identity a) -> t m a

As far as I understand monad transformers, these functions should always exist and be fairly straightforward, right?

With these I could obviously implement transmap and transreturn:

transmap :: (MonadTrans t, Monad m) => (forall b. m b -> m b) -> t m a -> t m a
transmap f x = toTrans (f (fromTrans x))

transreturn :: (MonadTrans t, Monad m) => m (t Identity a) -> t m a
transreturn = toTrans

I am sure there is something obvious that I am overlooking. Please point at it for me.

Thanks.

Upvotes: 3

Views: 298

Answers (4)

dfeuer
dfeuer

Reputation: 48581

It appears that one concept you're seeking can be found in the mmorph package:

class MFunctor t where
  -- The argument is generally required to be a monad morphism,
  -- but some instances will work sensibly when it's not.
  hoist :: Monad m => (forall x. m x -> n x) -> t m a -> t n a

This is a little more general than your version because it allows the underlying monad to be replaced.

Upvotes: 3

Juan Casanova Jaquete
Juan Casanova Jaquete

Reputation: 123

After a while, I finally came up with exactly what I was looking for since the beginning. I can use StateT exactly the way I wanted, and it has exactly the semantics I thought it had, but I did not explain it well (and put mistakes in what I wrote).

Going back to my original post, I need not have a State as input, the State/StateT monad already includes the input in the monadic element. So what I needed was a function recFuncT :: [t] -> StateT s m a that behaved equivalently to the following non-transformer one:

recFunc :: a -> [t] -> m (State s a)
recFunc x [] = return (return x)
recFunc x (t:ts) = myFunc (recFunc x ts)

It can be implemented directly, using the constructor StateT and runStateT. Here it is:

recFuncT :: a -> [t] -> StateT m s a
recFuncT x [] = return x
recFuncT x (t:ts) = StateT (\s -> myFunc (runStateT (recFuncT x ts) s))

Moreover, the function transmap can also be implemented in general, at least for StateT:

transmap :: Monad m => (forall b. m b -> m b) -> StateT s m a -> StateT s m a
transmap f st = StateT (\s -> f (runStateT st s)

And then we could write recFuncT nicely in terms of it:

recFuncT :: a -> [t] -> StateT m s a
recFuncT x [] = return x
recFuncT x (t:ts) = transmap myFunc (recFuncT x ts)

I realize this does not really match with the code that I included originally, but it does match with the overall principle I was trying to appeal to saying that the StateT transformer is like adding state to my monad m, and therefore anything that can be done at the m (State s a) level can be done at the StateT s m a level.

Upvotes: 0

Juan Casanova Jaquete
Juan Casanova Jaquete

Reputation: 123

EDIT: All of the below makes no sense. I leave it strikedthrough. Good answer below.

For the record, my final solution was neither to use or implement a monad transformer, and instead simply implement the following function: (my custom monad is called EnumProc):

(..>>=) :: Monad m => EnumProc (m a) -> (a -> m b) -> EnumProc (m b)
en ..>>= f = en <$> (>>= f)
infixl 7 ..>>=

This allows me to deal with monadic computations inside my monad while keeping the outside monad structure. I was surprised myself when an fmap sufficed.

I then use EnumProc (State s a) as type throughout.

Upvotes: 0

K. A. Buhr
K. A. Buhr

Reputation: 50819

From the discussion in the comments, it sounds like what you really want is a monad transformer for your custom monad that is then applied to the base monad State. In other words, to the extent that your custom monad is "nearly" a list:

newtype Listish a = Listish [a]

its transformer version would have type:

newtype ListishT m a = ListishT [m a]

and so your final monad transformer stack would be:

type M s = ListishT (State s)

which is isomorphic to your monad stack

[State s a]  AKA  Listish (State s a)

Be sure not to over-generalize the pattern for creating a transformer from an underlying monad, however. While transformers for some monads:

newtype List a = List [a]
newtype Reader r a = Reader (r -> a)

are sensibly derived by replacing "a" with "m a":

newtype ListT m a = ListT [m a]
newtype ReaderT r m a = ReaderT (r -> m a)

transformers for other types are derived differently. For example:

newtype State s a = State (s -> (a, s))
newtype Writer w a = Writer (a, w)

give:

newtype StateT s a = StateT (s -> m (a, s))
-- **NOT** StateT (s -> (m a, s))
newtype WriterT s a = WriterT (m (a, w))
-- **NOT** WriterT (m a, w)

In particular, there is no monad transformer for IO, because the simple substitution

newtype BadIOT m a = BadIOT (IO (m a))

is, as you point out, silly.

Upvotes: 2

Related Questions