Open jacobstanley opened 7 years ago
When you say "deriving MMonad
", are you literally trying this?
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
newtype FooT m a = FooT { unFoo :: ExceptT () (StateT Int (BarT m)) a }
deriving MMonad
If so, that doesn't work by design, as you can't eta-reduce m a
from ExceptT () (StateT Int (BarT m)) a
. You could do this, however, if you slightly refactor FooT
:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TypeOperators #-}
import Control.Monad.Trans.Compose
newtype FooT m a = FooT { unFoo :: (ExceptT () `ComposeT` StateT Int `ComposeT` BarT) m a }
deriving MMonad
Now m a
appears unencumbered on the RHS, so it's possible to eta-reduce it for the purposes of deriving MMonad
!
...except that mmorph
currently doesn't provide MMonad
instances for ExceptT
and StateT
. That should probably be fixed...
When you say "deriving MMonad", are you literally trying this?
No, I mean deriving by hand.
You could do this, however, if you slightly refactor FooT
Interesting! Are there any downsides to that?
Are there any downsides to that?
I suppose there's a downside in the sense that if you want to define other common mtl
-like instances for FooT
, you'll also need to make sure that ComposeT
has a corresponding instance as well. Defining instances for ComposeT
tends to be pretty straightforward in my experience, although I've never found a way to implement MonadTransControl
or MonadBaseControl
for ComposeT
... maybe someone smarter than me can figure it out :)
Hmm damn, GHC can't typecheck the derived code, I'll see if I can get an example together when I get time.
Actually, I fear that I might have lead you astray with bad advice. You wouldn't just need MMonad
instances for ExceptT
and StateT
, you'd also need one for ComposeT
as well. And as far as I can tell, creating a MMonad
instance for ComposeT
is impossible in current GHC, as you'd need a constraint like forall m . (Monad m) => Monad (g m)
, which would require something like implication constraints. Oh well.
But this is a red herring, since you said you weren't using GeneralizedNewtypeDeriving
in the first place. So, let me try to get back on track... what were you trying to do? :)
I was just trying to implement MMonad
manually for a rather large stack, and I couldn't figure out how to use the fact that each transformer in the stack is already MMonad
.
I just ended up unwrapping/running everything, doing the bind, and then putting it all back together, which was very unsatisfying and error prone. You end up with the bind for every transformer, all smashed together in the same function.
Is there a way to implement MMonad
using the embed
functions which the transformers already have, or will I run in to the same issue as ComposeT
?
Maybe try with ExceptT () (WriterT [()] m)
as an example?
@jystic: Here's the general trick:
import Control.Monad.Trans.Identity
import Control.Monad.Morph
newtype Example m a =
Example { runExample :: IdentityT (IdentityT (IdentityT m)) a }
instance MFunctor Example where
hoist f (Example m) = Example (hoist (hoist (hoist f)) m)
instance MonadTrans Example where
lift m = Example (lift (lift (lift m)))
instance MMonad Example where
embed f m =
Example (embed (embed (embed (runExample . f))) (runExample m))
@Gabriel439, thanks for the help.
I can't seem to get that to type check if I change one of the IdentityT
transformers to a different transformer, say ExceptT
.
I'm convinced @Gabriel439's example only works precisely because it's using the same monad transformer repeatedly. As @jystic discovered, if you try swapping out one of the IdentityT
s out with a different transformer, then the iterated embed
trick no longer works.
I'm convinced that doing this in a compositional way is tantamount to writing a MMonad
instance for ComposeT
which, as noted in https://github.com/Gabriel439/Haskell-MMorph-Library/issues/34#issuecomment-288390248, is currently impossible. So I don't see any other way to do this other than the all-at-once approach from https://github.com/Gabriel439/Haskell-MMorph-Library/issues/34#issue-215918776.
Oops, you're right. In fact, it shouldn't be possible in general because MMonad
s don't compose for the same reason that Monad
s don't compose: you need a distributive law. The reason my example worked is because IdentityT
s (trivially) distribute
So then this comes back to my original question:
Am I missing something, or is this because monads don't compose? Is there an equivalent of monad transformers for the category of monads?
Is something like MMonad
transformers possible? What would that even look like?
My intuition is the higher-kinded analogy would be something like this:
{-# LANGUAGE KindSignatures #-}
import Control.Monad.Trans.Except
import Control.Monad.Morph
newtype ExceptTT e t (m :: * -> *) a = ExceptTT { runExceptTT :: t (ExceptT e m) a }
instance MFunctor t => MFunctor (ExceptTT e t) where
hoist f (ExceptTT m) = ExceptTT (hoist (hoist f) m)
instance (MFunctor t, MonadTrans t) => MonadTrans (ExceptTT e t) where
lift m = ExceptTT (hoist lift (lift m))
instance MMonad t => MMonad (ExceptTT e t) where
embed f = ???
... but I think I need more constraints on t
to implement the final MMonad
instance. I got stuck trying to implement it
...except that mmorph currently doesn't provide MMonad instances for ExceptT and StateT. That should probably be fixed...
Can StateT
be given an MMonad
instance? I thought it was not possible.
@edsko: You're right: StateT
cannot be given an MMonad
instance
Given some random transformer stack, where all of the transformers implement
MMonad
:I'm finding it difficult to create instances of
MMonad
forFoo
, without just running everything and manually implementingembed
for the entire stack.Am I missing something, or is this because monads don't compose? Is there an equivalent of monad transformers for the category of monads? Maybe that would make creating these instances easier.
Any thoughts appreciated, hopefully I'm just missing something obvious.