在与我之前的问题相同的场景中,我试图使用fold仅实现cycle
函数,这时我想出了下面的错误函数,该函数试图将累加器与自身连接起来,以指数形式构建无限列表(是的,我知道这意味着如果想要take
1025个副本,它将生成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
的第二个参数被完全忽略。此外,如果这个折叠函数实际上并没有查看列表中的元素,那么真正发生的事情就是在它内部无限嵌套f
:fix 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..]