Suppose I have two monad transformers
T1 :: (* -> *) -> * -> *
T2 :: (* -> *) -> * -> *
with instances
instance MonadTrans T1
instance MonadTrans T2
and some
X :: (((* -> *) -> * -> *) -> ((* -> *) -> * -> *) -> * -> *)
such as
newtype X t1 t2 a b = X { x :: t1 (t2 a) b }
for which I would like to define something along the lines
instance (MonadTrans t1, MonadTrans t2) => MonadTrans (X t1 t2) where
lift = X . lift . lift
so that I can use lift to lift m a into X T1 T2 m a?
The problem here seems to be that the lifts act on some monad Monad m => m a which I cannot guarantee to be produce in the intermediate steps. But this is confusing for me. I am providing an implementation of lift so I can assume I have Monad m => m a, so I apply the rightmost lift and get T1 m a that I know nothing about, but shouldn't it be implied that T1 m is a Monad? If not why cannot I simply add this to the constraint of my instance
instance ( MonadTrans t1
, MonadTrans t2
, Monad (t2 m) ) => MonadTrans (X t1 t2) where ...
This does not work either. I have an intuition that by the above I am saying "should there be t1, t2, m such that ..." which is too weak to prove that X t1 t2 is a transformer (works for any/all Monad m). But it still doesn't make much sense to me, can a valid monad transformer produce a non-monad when applied to a monad? If not, I should be able to get away with the instance of MonadTrans (X t1 t2).
Is there a trick to do it that just eludes me or is there a reason why it cannot be done (theoretical or a limitation of what current compilers support).
Would the implication correspond to anything other than
instance (MonadTrans t, Monad m) => Monad (t m) where
return = lift . return
a >>= b = ... # no sensible generic implementation
which would overlap other instances / could not provide the specific bind? Couldn't this be worked around with some indirection? Making returnT :: Monad m => a -> t m a and bindT :: Monad m => t m a -> (a -> t m b) -> t m b part of MonadTrans so that one can then write
instance MonadTrans (StateT s) where
lift = ...
returnT = ...
bindT = ...
...
instance (MonadTrans t, Monad m) => Monad (t m) where
return = returnT
a >>= b = a `bindT` b
This kind of instances is not currently valid due to overlapping, would they however be feasible, useful?