I am experimenting with an mtl-style class that allows me to lift Pipe composition over an outer monad. To do so, I must define which two variables of the type are the domain and codomain of Pipe composition.

Hmmm...it may be clearer to change GPipe () to GPipe Void and declare (data GStateT s1 s2) and change GPipe (s1,s2) to GPipe (GStateT s1 s2). Then it is more obvious that GPipe is open to adding other monad stacks.
–
Chris KuklewiczAug 23 '12 at 11:27

The first solution I've already tried out: github.com/Gabriel439/Haskell-Pipes-Library/blob/pipetrans/…. It works perfectly, BUT, then I have to reimplement the entire transformers package so there is no code reuse. The data family approach looks like it should work. It seems pretty elegant, and I like the fact that it makes the layering explicit. I will have a chance to test it thoroughly this afternoon. I think your comment has a mistake, though, because then you would have a different number of parameters for each type.
–
Gabriel GonzalezAug 23 '12 at 14:45

My point was that () for Pipe and (,) for LPipe/StateT was arbitrary. I should make a new empty type for each GPipe instance, where each new empty type can have whatever number of parameters might be needed.
–
Chris KuklewiczAug 23 '12 at 19:58

Oh, I understand what you mean now. Right now, I think I'm relenting on something similar to your first solution using the following generalized newtype to rearrange the type variables: C t p a b r = C { unC :: t (p a b) r }. This does involve some newtype wrapping and unwrapping, but I think I can abstract even that away with some cleverness. Let me spend more time on it first.
–
Gabriel GonzalezAug 23 '12 at 22:03

I accepted this because the first answer you gave ended up being the closest to what actually solved my problem. Also, to answer your question about leftovers, I'll just leave this here. :)
–
Gabriel GonzalezAug 24 '12 at 18:49

Type inference is, by default, a guessing game. Haskell's surface syntax makes it rather awkward to be explicit about which types should instantiate a forall, even if you know what you want. This is a legacy from the good old days of Damas-Milner completeness, when ideas interesting enough to require explicit typing were simply forbidden.

Let's imagine we're allowed to make type application explicit in patterns and expressions using an Agda-style f {a = x} notation, selectively accessing the type parameter corresponding to a in the type signature of f. Your

idT = StateT $ \ s -> idT

is supposed to mean

idT {a = a}{m = m} = StateT $ \ s -> idT {a = a}{m = m}

so that the left has type C a a (StateT s m) r and the right has type StateT s (C a a m) r, which are equal by definition of the type family and joy radiates over the world. But that's not the meaning of what you wrote. The "variable-rule" for invoking polymorphic things requires that each forall is instantiated with a fresh existential type variable which is then solved by unification. So what your code means is

but that doesn't simplify, nor should it, because there is no reason to assume C is injective. And the maddening thing is that the machine cares more than you about the possibility of finding a most general solution. You have a suitable solution in mind already, but the problem is to achieve communication when the default assumption is guesswork.

There are a number of strategies which might help you out of this jam. One is to use data families instead. Pro: injectivity no problem. Con: structor. (Warning, untested speculation below.)

That's just a crummy way of making the type applications explicit. Note that some people use a itself instead of Proxy a and pass undefined as the argument, thus failing to mark the proxy as such in the type and relying on not accidentally evaluating it. Recent progress with PolyKinds may at least mean we can have just one kind-polymorphic phantom proxy type. Crucially, the Proxy type constructors are injective, so my code really is saying "same parameters here as there".

But there are times when I wish that I could drop to the System FC level in source code, or otherwise express a clear manual override for type inference. Such things are quite standard in the dependently typed community, where nobody imagines that the machine can figure everything out without a nudge here and there, or that hidden arguments carry no information worth inspecting. It's quite common that hidden arguments to a function can be suppressed at usage sites but need to be made explicit within the definition. The present situation in Haskell is based on a cultural assumption that "type inference is enough" which has been off the rails for a generation but still somehow persists.

It only ever takes a paragraph or two to recognize one of your answers. Love your style.
–
Daniel WagnerAug 23 '12 at 9:39

5

Kind of you to say so. I am also one of the least anonymous referees in the business...
–
pigworkerAug 23 '12 at 9:47

Thanks for explaining the type error. I now have a greater appreciation for the machine as an assistant and not an oracle. I tried the associated data type, but it requires explicit wrapping/unwrapping to use and I couldn't find a way to automate that for the user. Initial prototypes that I had in mind incorporated an isomorphism between the monad transformer and the associated data type so that this could be done automatically, but I could never get it to work.
–
Gabriel GonzalezAug 23 '12 at 14:53