Just to be clear when you write:
_pa :: PA a -> A a
The compiler expands type synonyms, and then moves the quantifiers and constraints up, for example:
_pa :: forall a. (forall m1. Monad m1 => Foo a -> m1 ()) -> (forall m2. Monad m2 => Foo a -> m2 ()) _pa :: forall m2 a. (Monad m2) => (forall m1. Monad m1 => Foo a -> m1 ()) -> Foo a -> m2 ()
So, _pa has a polymorphic type of rank-2, because it has an arrow built into the left side of the function. The same goes for _pfa . They consider polymorphic functions to be arguments.
To answer the real question, I will first show you something strange. Both typecheck types:
_pp :: PPFA a -> A a _pp x = _pa $ _pfa x _pp :: PPFA a -> A a _pp x = _pa (_pfa x)
This, however, does not mean:
apply :: (a -> b) -> a -> b apply fx = fx _pp :: PPFA a -> A a _pp x = apply _pa (_pfa x)
Unintuitive, right? This is because the application operator ($) has a special wrapper in the compiler to allow you to instantiate your type variables with polymorphic types, to support runST $ do { … } rather than runST (do { … }) .
The composition (.) , However, does not have a special cover. Therefore, when you call (.) On _pa and _pfa , it first creates its types. So you are trying to pass a non-polymorphic result _pfa type (Foo a0 -> m0 ()) -> Foo a -> m () mentioned in your error message to the _pa function, but it expects a polymorphic argument of type _pa , so you get a join error.
undefined :: a does not typecheck because it is trying to create an instance of a with a polymorphic type, an example of unproductive polymorphism. This is a hint of what you should do - the standard way to hide consistency - with the newtype wrapper:
newtype A a = A { unA :: forall m. Monad m => Foo a -> m () } newtype PA a = PA { unPA :: forall m. Monad m => Foo a -> m () } newtype PPFA a = PPFA { unPPFA :: forall m. Monad m => Foo a -> m () }
Now this definition compiles without errors:
_pp :: PPFA a -> A a _pp = _pa . _pfa
With a cost that you need to explicitly wrap and deploy to tell the GHC when to abstract and instantiate:
_pa :: PA a -> A a _pa x = A (unPA x)