First of all: what you want is impossible. You correctly identified the problem: even if m is a monad and t is a transformer, no one guarantees that tm will be a monad.
On the other hand, this is usually the case. But, at least theoretically - not always, so you will have to somehow note cases where this is so. This means that it marks it with an instance of another type, which you will need to determine yourself. See how it works.
Let's start with the name for the new class:
class MonadTrans t => MonadTransComposeable t where
Now, where what? We want to create an instance of Monad (tm) . Unfortunately, this is not something we can do. Class instances, being just data, are not considered the same data as everything else; we cannot create a function that generates them. So we have to get around this somehow. But if we have such a thing, then the class is pretty simple
class MonadTrans t => MonadTransComposeable t where transformedInstance :: Monad m => MonadInstance (tm)
Now define MonadInstance . We want to make sure that if there is MonadInstance n , then n is a monad. GADT come to the rescue:
data MonadInstance n where MonadInstance :: Monad n => MonadInstance n
Note that the MonadInstance constructor has a context that ensures that we cannot make MonadInstance n without n Monad .
Now we can define instances of MonadTransComposeable :
instance MonadTransComposeable (StateT s) where transformedInstance = MonadInstance
It will work because it is already installed in the transformers package, when whenever m is a monad, StateT m also a monad. Therefore, the MonadInstance constructor makes sense.
Now we can compose MonadTrans and MonadTransComposeable . Using your own definition
newtype X t1 (t2 :: (* -> *) -> (* -> *)) ma = X { x :: t1 (t2 m) a }
we can define an instance. Now we can prove that t2 m is a monad; it is not automatic, and we must tell Haskell what transformedInstance use, but it is not difficult:
instance (MonadTrans t1, MonadTransComposeable t2) => MonadTrans (X t1 t2) where lift :: forall m a. (Monad m) => ma -> X t1 t2 ma lift ma = case transformedInstance :: MonadInstance (t2 m) of MonadInstance -> X (lift (lift ma))
Now do something funny. Let Haskell say that when t1 and t2 are βgoodβ (compound) monad transformers, X t1 t2 too.
As before, it's pretty simple:
instance (MonadTransComposeable t1, MonadTransComposeable t2) => MonadTransComposeable (X t1 t2) where transformedInstance = MonadInstance
Same as with StateT . The trick is that Haskell will now complain that he cannot know if X t1 t2 m is really a monad. Which is fair - we did not define an instance. Let me do it.
We will use the fact that t1 (t2 m) is a monad. So we make it explicit:
transformedInstance2 :: forall t1 t2 m. (MonadTransComposeable t1, MonadTransComposeable t2, Monad m) => MonadInstance (t1 (t2 m)) transformedInstance2 = case transformedInstance :: MonadInstance (t2 m) of MonadInstance -> transformedInstance
Now we define an instance of Monad (X t1 t2 m) . Due to the stupid decision to make Monad subclass of Applicative , we cannot do it in one expression, but must go through Functor and Applicative , but this is not difficult:
instance (MonadTransComposeable t1, MonadTransComposeable t2, Monad m) => Functor (X t1 t2 m) where fmap h (X ttm) = case transformedInstance2 :: MonadInstance (t1 (t2 m)) of MonadInstance -> X (fmap h ttm) instance (MonadTransComposeable t1, MonadTransComposeable t2, Monad m) => Applicative (X t1 t2 m) where pure a = case transformedInstance2 :: MonadInstance (t1 (t2 m)) of MonadInstance -> X (pure a) (X ttf) <*> (X tta) = case transformedInstance2 :: MonadInstance (t1 (t2 m)) of MonadInstance -> X (ttf <*> tta)
and finally
instance (MonadTransComposeable t1, MonadTransComposeable t2, Monad m) => Monad (X t1 t2 m) where X tta >>= f = case transformedInstance2 :: MonadInstance (t1 (t2 m)) of MonadInstance -> X (tta >>= \a -> case fa of X ttb -> ttb)