RankNTypes与Daisy Chaining:Haskell类型统一错误求解
编译器处理逻辑
启用ImpredicativeTypes(包含RankNTypes)后,编译器允许多态类型出现在元组、列表这类通常要求单态类型的位置,但对forall q r . ((q->r), (r->r))这类Rank2类型有严格约束:元组的两个组件必须适配任意的q和r,意味着f和g的类型必须是完全多态的,不能被绑定到特定的q/r实例上。
错误原因
你的测试用例中,(\x -> length x)的类型是String -> Int(实际是Foldable t => t a -> Int,但此处被上下文约束为String -> Int),(\x -> x+1)是Int -> Int。这两个函数的类型被固定绑定到了q=String、r=Int,但你的类型签名要求传入的元组必须满足forall q r——即能适配任意q和r,显然测试用例无法满足这个约束,编译器无法完成类型统一,因此报错。
而移除forall后,类型签名变为(q->r, r->r),编译器会根据传入的参数自动推断出q=String、r=Int,自然能正常编译。
可行解决方案
方案1:调整类型签名适配实际场景
你真正需要的是“元组中的f能接收上一步输出,g能处理f的输出”,而非“适配任意q/r的元组”。可以修改单参数版本的类型签名:
z :: a -> (a -> r, r -> r) -> r z a (f,g) = g . f $ a
列表版本可通过折叠实现链式类型衔接,让每一步的输出作为下一步的输入:
z :: a -> [forall x y. (x -> y, y -> y)] -> b z = foldl' (\acc (f,g) -> g . f $ acc)
这个版本仍需ImpredicativeTypes,且每个元组的f必须能接收上一步的acc类型。
方案2:用存在类型表达“特定类型的元组”
如果想保留“每个元组处理特定类型”的灵活性,可使用存在类型替代Rank2的全称量词:
{-# LANGUAGE ExistentialQuantification #-} data FG = forall q r. FG (q -> r, r -> r) z :: a -> [FG] -> b z = foldl' (\acc (FG (f,g)) -> g . f $ acc)
此时每个FG实例可对应不同的q/r对,只要f能接收上一步的结果即可。测试用例可修改为z "Hello World" [FG ((\x->length x), (\x->x+1))],能正常编译运行。
方案3:放弃Rank2约束(最简方案)
如果不需要处理完全多态的元组,直接移除forall,让编译器自动推断类型:
z :: a -> ((a -> r), (r -> r)) -> r z a (f,g) = g . f $ a
这种方式无需启用额外扩展,测试用例可直接正常运行。
内容的提问来源于stack exchange,提问作者CodeWeaver

