Haskell中seq函数如何评估无限列表?seq [1..] 5为何返回5?
为什么
seq [1..] 5不会死循环? 好问题!这其实戳中了Haskell惰性求值和seq严格性规则的核心,很多刚摸严格性工具的开发者都会在这里卡壳。
首先得明确seq的真实行为:它只强制第一个参数求值到「弱头范式(Weak Head Normal Form, WHNF)」,而不是把整个表达式完全展开求值。那WHNF对于列表来说是什么意思?
简单说,列表的WHNF只要求我们知道它是两种构造器中的哪一种:
- 要么是空列表
[] - 要么是
(:)构造器(也就是x : xs的形式,不管x和xs有没有被求值)
现在看[1..],它本质是1 : [2..]——这个表达式的构造器是(:),而且这个构造器是可以立即确定的,不需要去递归构造[2..]、[3..]这些后续元素。当seq处理[1..]时,只需要确认“哦,这是个(:)构造的列表”,就完成了对第一个参数的强制求值,接下来直接返回第二个参数5,根本不会碰后面的无限元素,自然不会陷入死循环。
那seq到底怎么处理无限列表?核心就是只停留在构造器层面:
- 不管列表是有限还是无限,
seq都不会递归展开它的后续元素; - 对于无限列表,只要它的顶层构造器是确定的(比如
[1..]的(:)),seq就完成了任务,不会继续求值剩下的thunk(未求值的表达式)。
这里可以对比下deepseq——如果换成deepseq [1..] 5,那真的会陷入死循环,因为deepseq会强制完全求值整个表达式,包括递归展开无限列表的每一个元素,这显然是不可能完成的。
举个更直观的例子:
ghci> seq [1..] 5 5 ghci> import Control.DeepSeq ghci> deepseq [1..] 5 -- 这里会一直卡着,直到你中断程序
总结一下:seq的严格性是“浅”的,只到顶层构造器;无限列表的顶层构造器可以立即确定,不需要展开全部元素,所以seq [1..] 5能直接返回5,不会死循环。
内容的提问来源于stack exchange,提问作者user3560270
相关产品推荐
相关产品推荐

