Haskell中`seq`会重复求值参数?现象与疑问
为什么
x seq x会重复求值? 这个问题的核心原因其实是GHC的单态限制(Monomorphism Restriction),咱们一步步拆解来看:
先回顾你的测试现象
你发现了两种截然不同的表现:
- 当用
let x = fib 30 in xseqx时,耗时是直接调用fib 30的两倍,内存占用也翻倍,明显重复计算了两次fib 30 - 但换成匿名函数版本
(\x -> xseqx) (fib 30),或者给x加类型注解let x = (fib 30) :: Int in xseqx时,耗时就和直接调用fib 30差不多了
单态限制在搞什么鬼?
GHCi默认开启单态限制,它的作用是把某些无显式类型注解的多态绑定转成单态绑定,避免重复计算。但这里的情况刚好反过来:
- 当你写
let x = fib 30时,fib 30的类型是Num a => a(多态的,因为fib的返回值可以是任何数值类型)。单态限制不会对这种“无显式类型注解的多态绑定”生效,所以x会被当成一个多态的thunk(未求值表达式)。 - 当你写
xseqx时,第一个x会被seq强制求值到WHNF,第二个x是一次新的引用——因为x是多态的,GHC会为每个引用生成不同的实例,所以第二个x会重新计算一遍fib 30,导致总耗时翻倍。
为什么另外两个测试案例正常?
- 匿名函数版本:
(\x -> xseqx) (fib 30)里的x是函数参数,参数是单态的(函数调用时fib 30会被实例化为具体数值类型,比如Integer)。seq强制求值x一次后,第二个x直接用已经求值完成的结果,不会重复计算。 - 加类型注解的版本:
let x = (fib 30) :: Int显式指定了x的类型是单态的Int,此时单态限制生效,GHC会把x绑定为单态thunk,第一次求值后就会缓存结果,所以xseqx里的第二个x直接复用缓存,不会重复计算。
验证方法:关闭单态限制试试
你可以在GHCi里输入:set -XNoMonomorphismRestriction关闭单态限制,然后再测试let x = fib 30 in x seq x,会发现耗时和直接调用fib 30几乎一样——因为此时GHC会把x当成单态绑定,求值一次后就缓存结果,两个x都会复用这个缓存。
再确认seq的行为
seq a b的规则是:先强制把a求值到WHNF,然后返回b。它本身不会导致重复求值,问题出在x的绑定是否被缓存——如果x是单态的、被缓存的thunk,那b(也就是第二个x)直接用缓存;如果x是多态的、未被缓存的thunk,那每次引用都会重新计算。
内容的提问来源于stack exchange,提问作者RandomStudent
相关产品推荐
相关产品推荐

