Haskell中命名字段的意外类型推断问题
咱们先回顾一下你给出的GHCi会话,方便对照:
GHCi, version 8.2.2: http://www.haskell.org/ghc/ :? for help Prelude> :set -XRankNTypes Prelude> data Functor f = Functor { fmap :: forall a b. (a -> b) -> f a -> f b } Prelude> :t fmap fmap :: Functor f1 -> (a -> b) -> f2 a -> f2 b Prelude> :t Functor map Functor map :: Functor [] Prelude> :t fmap (Functor map) fmap (Functor map) :: (a -> b) -> [a] -> [b]
你疑惑的点在于:为什么:t fmap显示的类型里f1和f2是独立的,没有约束它们相等,但实际应用后却能得到符合预期的(a->b)->[a]->[b]?这背后是GHC在RankNTypes扩展下的类型推断策略在起作用,咱们一步步拆解:
1. 你的Functor定义是Rank-2多态类型
你定义的Functor数据类型的fmap字段是一个Rank-2多态函数:
data Functor f = Functor { fmap :: forall a b. (a -> b) -> f a -> f b }
这里的forall a b嵌套在字段类型里,意味着这个fmap字段只能接受针对特定f类型的映射函数——比如map是针对列表的,所以Functor map的类型是Functor [],它的fmap字段只能处理[]类型的值。
2. GHCi显示的类型是“最泛化的外部视图”
当你用:t fmap查询记录访问器的类型时,GHC给出的是最泛化的类型签名:
fmap :: Functor f1 -> (a -> b) -> f2 a -> f2 b
这里的f1和f2看起来是无关的,但这只是GHC显示类型的方式——它没有显式写出隐含的约束:当你传入一个Functor f1的值后,返回的函数只能处理f1类型的值(也就是f2必须等于f1)。
这个约束是由Functor f1内部的fmap字段类型强制的:每个Functor f1值的fmap字段都是forall a b. (a->b)->f1 a->f1 b,它只能操作f1类型的容器,所以当你把访问器fmap应用到Functor f1后,返回的函数必然只能处理f1类型的值,f2也就被绑定到了f1。
3. 应用时的类型统一验证了约束
当你执行fmap (Functor map)时,GHC会做以下事情:
- 识别
Functor map的类型是Functor [],所以f1被绑定为[] - 因为
Functor []的fmap字段只能处理列表类型,所以f2也被强制绑定为[] - 最终得到的类型就是
(a->b)->[a]->[b],完全符合预期
如果你尝试打破这个隐含约束(比如把Functor Maybe的fmap应用到列表上),GHC会直接抛出类型错误,比如:
Prelude> let maybeF = Functor (\f x -> fmap f x) :: Functor Maybe Prelude> fmap maybeF (+1) [1,2,3] -- 报错:无法将类型 `Maybe' 与 `[]' 匹配
这直接证明了f1和f2必须相等的隐含约束是存在的,只是GHCi在显示访问器的泛化类型时没有明确写出来。
内容的提问来源于stack exchange,提问作者Aadit M Shah

