Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a principled way to compose two monad transformers if they are of different type, but their underlying monad is of the same type?

Not much I can do to expand the question. But here is a use case: let's say you have two monad transformers, t and s, transforming over the same monad m:

master :: (MonadTrans t, Monad m) => t m a b
slave  :: (MonadTrans t, Monad m) => s m a b

And I want to compose master and slave such that they can communicate with each other when m primitives are lifted into t and s. The signature might be:

bound :: (MonadTrans t, MonadTrans s, Monad m, Monoid a) => t m a b -> s m a b -> (...)
But what is the type of (...) ?

A use case, in sugared notation:

master :: Monoid a => a -> t m a b
master a = do 
   a <- lift . send $ (a,False)     -- * here master is passing function param to slave
   ...                              -- * do some logic with a
   b <- lift . send $ (mempty,True) -- * master terminates slave, and get back result

slave :: Monoid a => (a -> b) -> s m a b
slave g = do 
    (a,end) <- lift receive
    case end of 
        True -> get >>= \b -> exit b  
        _    -> (modify (++[g a])) >> slave g

Update: send and receive are primitives of type m.

I apologize if this example looks contrived, or resemble coroutines too much, the spirit of the question really has nothing to do with it so please ignore all similarities. But main point is that monads t and s couldn't be sensibly composed with each other before, but after both wrap some underlying monad m, they now could be composed and run as a single function. As for the type of the composed function, I'm really not sure so some direction is appreciated. Now if this abstraction already exist and I just don't know about it, then that would be best.

like image 739
xiaolingxiao Avatar asked Aug 21 '13 18:08

xiaolingxiao


People also ask

Why Monad Transformers?

Monad transformers not only make it easier to write getPassphrase but also simplify all the code instances.

What is MTL Haskell?

From HaskellWiki. The MTL provides a selection of monads and their transformer variants along with type classes that allow uniform handling of a base monad and its transformer.

What is liftIO?

liftIO allows us to lift an IO action into a transformer stack that is built on top of IO and it works no matter how deeply nested the stack is.

What is Writer Monad?

The Writer monad is a programming design pattern which makes it possible to compose functions which return their result values paired with a log string. The final result of a composed function yields both a value, and a concatenation of the logs from each component function application.


1 Answers

Yes. Combine hoist from the mmorph package with lift to do this:

bound
    :: (MonadTrans t, MonadTrans s, MFunctor t, Monad m)
    => t m () -> s m () -> t (s m) ()
bound master slave = do
    hoist lift master
    lift slave

To understand why this works, study the type of hoist:

hoist :: (MFunctor t) => (forall x . m x -> n x) -> t m r -> t n r

hoist lets you modify the base monad of any monad transformer that implements MFunctor (which is most of them).

What the code for bound does is have the two monad transformers agree on a final target monad, which in this case is t (s m). The order in which you nest t and s is up to you, so I just assumed that you wanted t on the outside.

Then it's just a matter of using various combinations of hoist and lift to get the two sub-computations to agree on the final monad stack. The first one works like this:

master :: t m r
hoist lift master :: t (s m) r

The second one works like this:

slave :: s m r
lift slave :: t (s m) r

Now they both agree so we can sequence them within the same do block and it will "just work".

To learn more about how hoist works, I recommend you check the documentation for the mmorph package which has a nice tutorial at the bottom.

like image 173
Gabriella Gonzalez Avatar answered Sep 25 '22 02:09

Gabriella Gonzalez