Haskell中Lambda表达式类型差异疑问:f2与f2'为何类型不同?
为什么f2和f2'的类型推导结果不一样?
嘿,这个问题的核心是Haskell里的单态限制(Monomorphism Restriction),再加上GHC在模块和GHCi中的默认设置差异,我来给你拆解清楚:
首先把你的代码再列出来,方便对比:
f x = x + 1 -- f :: Num a => a -> a f' = \x -> x + 1 -- f' :: Num a => a -> a f2 a b = a + b -- f2 :: Num a => a -> a -> a f2' = \a b -> a + b -- f2' :: Integer -> Integer -> Integer(模块中默认推导)
1. 什么是单态限制?
单态限制是Haskell 98标准里的一条类型推导规则,初衷是避免某些不必要的多态带来的性能开销。简单来说:
- 对于顶层的无参数绑定(也就是像
f' = ...、f2' = ...这种,没有显式写出参数的绑定),如果它的类型可以被泛化(有多态类型,比如Num a => ...),但没有显式的类型签名,编译器会默认把它实例化为一个单态类型(具体的类型,比如Integer)。 - 而对于带显式参数的函数绑定(比如
f x = ...、f2 a b = ...),编译器会直接推导多态类型,不会触发单态限制。
2. 为什么f'没问题,但f2'被推导为Integer?
这里要注意GHC的默认设置差异:
- 在GHCi中,默认是开启
NoMonomorphismRestriction扩展的,单态限制不会生效,f2'会被推导为多态类型Num a => a -> a -> a,和f2完全一致。 - 但在普通模块(也就是你写
.hs文件编译的场景)中,默认是关闭NoMonomorphismRestriction的,单态限制会生效。这时候f2' = \a b -> a + b是无参数的顶层绑定,编译器会把Num a约束中的a实例化为Num类的默认类型——Integer,所以最终类型变成了Integer -> Integer -> Integer。
至于f' = \x -> x + 1能保持多态,是因为Haskell对单个lambda表达式的无参数绑定有特殊处理,会把它当作函数绑定看待,不会触发单态限制。
3. 怎么让f2'和f2保持一致?
有两种简单方法:
- 方法一:添加显式类型签名:直接给
f2'指定多态类型,编译器会完全尊重你的声明:f2' :: Num a => a -> a -> a f2' = \a b -> a + b - 方法二:关闭单态限制:在模块开头添加扩展声明,让所有顶层无参数绑定都推导为多态类型,和GHCi默认行为一致:
{-# LANGUAGE NoMonomorphismRestriction #-}
4. 验证一下
如果你在模块中开启NoMonomorphismRestriction,再查看f2'的类型,就会和f2一样是Num a => a -> a -> a;关闭该扩展的话,f2'就会回到Integer -> Integer -> Integer,和你遇到的情况完全匹配。
内容的提问来源于stack exchange,提问作者DonJoe
相关产品推荐
相关产品推荐

