Type input is, by default, a guessing game. Haskell's surface syntax makes it rather inconvenient to explicitly know what types an forall instance should create, even if you know what you want. This is a legacy from the good old days of the fullness of Damas-Milner, when ideas interesting enough to require explicit typing were simply forbidden.
Suppose we are allowed to make an application of a type explicit in patterns and expressions using the Agda-style f {a = x} notation, selectively accessing the type parameter corresponding to a in a signature of type f . Your
idT = StateT $ \ s -> idT
supposed to
idT {a = a}{m = m} = StateT $ \ s -> idT {a = a}{m = m}
so the left one is of type C aa (StateT sm) r , and the right one is of type StateT s (C aam) r , which is equal by definition to the type family, and joy radiates around the world. But that is not the point of what you wrote. A “variable rule” for calling polymorphic things requires that each forall be created with a new variable of the existential type type, which is then solved by unification. So what your code means
idT {a = a}{m = m} = StateT $ \ s -> idT {a = a'}{m = m'} -- for a suitably chosen a', m'
The available restriction after computing a type family is
C aam ~ C a' a' m'
but this does not simplify and should not, because there is no reason to assume that C is injective. And the madness is that the car cares more about the opportunity to find the most common solution. You already have a suitable solution, but the problem is reaching the connection when the default assumption is a hunch.
There are many strategies that can help you with this common cold. One of them is to use data families. Pro: injectivity is not a problem. Con: structor. (Warning, unverified spec below.)
class MonadPipe m where data C ab (m :: * -> *) r idT :: C aamr (<-<) :: C bcmr -> C abmr -> C acmr instance (MonadPipe m) => MonadPipe (StateT sm) where data C ab (StateT sm) r = StateTPipe (StateT s (C abm) r) idT = StateTPipe . StateT $ \ s -> idT StateTPipe (StateT f) <-< StateTPipe (StateT g) = StateTPipe . StateT $ \ s - fs <-< gs
Another convention is that the resulting data family is not automatically monadic, and it is very easy to deploy or make it a monadic uniform way.
I'm thinking of trying a template for these things where you keep your type family, but define a newtype wrapper for it
newtype WrapC abmr = WrapC {unwrapC :: C abmr}
then use WrapC in operation types to keep typechecker in the right way. I do not know if a good strategy, but I plan to find out the other day.
A more direct strategy is to use proxies, phantom types, and type variables (although they don't need this example). (Again, a warning about speculation.)
data Proxy (a :: *) = Poxy data ProxyF (a :: * -> *) = PoxyF class MonadPipe m where data C ab (m :: * -> *) r idT :: (Proxy a, ProxyF m) -> C aamr ... instance (MonadPipe m) => MonadPipe (StateT sm) where data C ab (StateT sm) r = StateTPipe (StateT s (C abm) r) idT pp = StateTPipe . StateT $ \ s -> idT pp
This is just a tricky way to make type applications explicit. Please note that some people use a their own instead of Proxy a and pass undefined as an argument, thereby not specifying the proxy server as such in the type and relying on its random evaluation. Recent progress with PolyKinds may at least mean that we can only have one type-polymorphic phantom proxy type. Basically, constructors like Proxy are injective, so my code really says "the same parameters as there."
But there are times when I want me to be able to go to the System FC level in the source code or otherwise express a clear manual override for type inference. Such things are completely standardized, depending on a typed community, where no one imagines that a machine can understand everything without a jerk here and there, or that hidden arguments do not contain any information that is worth checking. It is very common that hidden function arguments can be suppressed on usage sites, but must be clearly defined in the definition. Haskell’s current situation is based on the cultural assumption that the “type of inference is enough,” which has been off the rails for a generation but is still somehow preserved.