只是为了清楚起见,当你写下:
_pa :: PA a -> A a
编译器扩展类型同义词,然后向上移动量词和约束,如下所示:
_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
具有 2 级多态类型,因为它有一个嵌套在函数箭头左侧的 forall。同样适用于_pfa
。他们期望多态函数作为参数。
为了回答实际问题,我首先向您展示一些奇怪的东西。这些都进行类型检查:
_pp :: PPFA a -> A a
_pp x = _pa $ _pfa x
_pp :: PPFA a -> A a
_pp x = _pa (_pfa x)
然而,这并没有:
apply :: (a -> b) -> a -> b
apply f x = f x
_pp :: PPFA a -> A a
_pp x = apply _pa (_pfa x)
不直观,对吧?这是因为应用程序运营商($)
在编译器中是特殊情况,允许使用多态类型实例化其类型变量,以支持runST $ do { … }
而不是runST (do { … })
.
作品(.)
然而,并不是特例。所以当你打电话时(.)
on _pa
and _pfa
,它首先实例化它们的类型。因此你最终试图通过非多态性的结果_pfa
,类型(Foo a0 -> m0 ()) -> Foo a -> m ()
错误消息中提到的函数_pa
,但它需要一个类型的多态参数P a
,所以你会得到一个统一错误。
undefined :: a
不进行类型检查,因为它尝试实例化a
具有多态类型,是谓述多态性的一个实例。这是关于你应该做什么的提示——隐藏必然性的标准方法是使用newtype
包装:
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 () }
现在这个定义编译没有错误:
_pp :: PPFA a -> A a
_pp = _pa . _pfa
您需要显式包装和展开以告诉 GHC 何时抽象和实例化的成本:
_pa :: PA a -> A a
_pa x = A (unPA x)