为什么使用fold生成指数增长的无限列表溢出



在与我之前的问题相同的场景中,我试图使用fold仅实现cycle函数,这时我想出了下面的错误函数,该函数试图将累加器与自身连接起来,以指数形式构建无限列表(是的,我知道这意味着如果想要take1025个副本,它将生成2048个副本(

myCycle :: [a] -> [a]
myCycle s = foldr (_ a -> a ++ a) s [1..]

但是,使用它会抛出*** Exception: heap overflow

相反,这个版本就像一个魅力

myCycle :: [a] -> [a]
myCycle s = foldr (_ a -> s ++ a) s [1..]

我的问题是,为什么前一个版本与后一个版本相比会溢出?我觉得原因比我更愚蠢…

[1] 我的意思是,把cycle实现为一个折叠,只有阶跃函数和种子作为自由度。

foldr c n获取一个列表,并用c替换每个(:),用n替换最后的[]。但是[1..]没有最终的[],所以foldr (_ a -> a ++ a) s没有地方放s。因此,从来没有信息从s"流"到myCycle s的结果,这意味着它别无选择,只能垫底(或者更确切地说:它有太多的选择——它没有被指定——所以它放弃了,回到了底部(。第二个版本实际上使用了s,因为它出现在折叠函数中,是在foldr作用于无限列表时使用的

事实上,我们有身份

foldr (_ -> f) x xs = fix f = let x = f x in x

当CCD_ 18为无穷大时。也就是说,当列表为无穷大时,foldr的第二个参数被完全忽略。此外,如果这个折叠函数实际上并没有查看列表中的元素,那么真正发生的事情就是在它内部无限嵌套ffix f = f (f (f (f ...)))fix是基本的,因为每种递归都可以用它来写(某些更奇特的递归需要添加一些语言扩展,但fix f = let x = f x in x本身的定义不会改变(。这使得根据foldr和无限列表编写任何递归函数都是琐碎的。

这是我对指数周期的看法。它生成输入的1个副本,连接到2个副本上,连接到4个副本上等等。

myCycle xs = xs ++ myCycle (xs ++ xs)

通过将递归调用抽象为参数并将其传递给fix:,可以将显式递归定义转换为fix

myCycle = fix rec xs -> xs ++ rec (xs ++ xs)

然后使用foldr身份并引入伪造的[]案例

myCycle = foldr (_ rec xs -> xs ++ rec (xs ++ xs)) (error "impossible") [1..]

最新更新