Haskell 对懒惰的困惑

Haskell 对懒惰的困惑,haskell,functional-programming,lazy-evaluation,Haskell,Functional Programming,Lazy Evaluation,我有一个函数 myLength = foldl (\ x _ -> x + 1) 0 当输入大约10^6个元素时,堆栈溢出失败(myLength[1..1000000]失败)。我相信这是因为当我用foldl'替换foldl'时,thunk的积累,它起作用了。 到目前为止还不错 但现在我有了另一个函数来反转列表: myReverse = foldl (\ acc x -> x : acc) [] 使用延迟版本foldl(而不是foldl')的 当我这样做的时候 myLength。m

我有一个函数

myLength = foldl (\ x _ -> x + 1) 0
当输入大约10^6个元素时,堆栈溢出失败(myLength[1..1000000]失败)。我相信这是因为当我用foldl'替换foldl'时,thunk的积累,它起作用了。 到目前为止还不错

但现在我有了另一个函数来反转列表:

myReverse = foldl (\ acc x -> x : acc) []
使用延迟版本foldl(而不是foldl')的

当我这样做的时候
myLength。myReverse$[1..1000000]
。 这次效果不错。我不明白为什么foldl适用于后一种情况而不适用于前一种情况


这里要澄清的是,myLength使用foldl'而myReverse使用foldl,这是我最好的猜测,尽管我还不是Haskell内部结构的专家

在构建thunk时,Haskell分配堆上的所有中间累加器变量

在执行
myLength
中的加法时,需要将堆栈用于中间变量。看见摘录:

当我们最终评估z1000000时,问题就开始了:

请注意,z1000000=z999999+ 1000000所以1000000被推到堆栈上。然后对z999999进行评估

请注意,z999999=z999998+999999。 因此999999被推到堆栈上。然后 z999998被评估为:

请注意,Z99998=Z99997+999998。 因此,99998被推到堆栈上。然后 z999997被评估为:

但是,在执行列表构造时,我认为会发生以下情况(这是猜测的开始):

评估z1000000时:

请注意,z1000000=1000000: z999999。所以1000000存储在里面 z1000000,以及一个链接(指针) 至z999999。然后对z999999进行评估

请注意,z999999=999999:z999998。 因此9999999存储在z999999中, 以及Z99998的链接。然后 对z999998进行了评价

等等

请注意,z999999、z999998等从尚未计算的表达式更改为单个列表项是Haskell每天都要做的事情:)


由于z1000000、Z9999999、z999998等都在堆上,因此这些操作不使用任何堆栈空间。QED。

这两种情况下都会出现堆栈溢出异常。不,这只是您正在查看的网站顶部的徽标;)(myReverse没有例外)实际上,
(:)
的两个参数都存储为指针,而不仅仅是尾部。换句话说,
(+)
在两个it参数中都是严格的(它们需要完全计算),但是
(:)
在参数中是懒惰的(它们可以是thunks)。