我在Haskell中编写了下面的函数,我不太清楚为什么它不能用于惰性求值。
infFib :: [Integer]
infFib = infFib' []
where
infFib' [] = infFib' [0,1]
infFib' (xs) = infFib' (xs ++ [(foldr (\a b -> a+b) 0 (take 2 (reverse xs)))])
答案 0 :(得分:5)
tl; dr因为infFib'
永远不会返回,它只会继续拨打电话。
我希望您在构建infFib'
时的想法大致如此:
不幸的是,你编写它的方式,你用一个更长更长的列表来称呼你自己 - 但你永远不会将那个列表(甚至部分)返回给调用者!您只需继续构建更长更长的列表并在其上调用infFib'
。
你应该做的是开始返回你有信心的列表部分,并增加你不自信的部分。例如:
infFib' [] = [0,1] ++ infFib' [0,1]
infFib' xs = newPart ++ infFib' (xs ++ newPart) where
newPart = [(foldr (\a b -> a+b) 0 (take 2 (reverse xs)))]
此时,您可以在ghci中测试infFib
实际上是否正常工作并产生正确的答案。如果这符合您的需求,您可以在此停留并称之为一天。
但是,与可能的情况相比,这种实现效率非常低:在这里,infFib'
获得更长更长的列表作为其参数,当它只关心最后两个元素时。然后它必须遍历整个列表才能访问它关心的元素,这会浪费时间和内存。让我们通过仅传递最后两个元素来解决这个问题。我们必须修复任何函数调用infFib'
- 幸运的是,在这种情况下,因为它本地作用于where
块,我们知道它只是两个,infFib'
本身和块的所有者infFib
。我们还必须内联[]
的基本情况,因为在这种情况下我们还没有两个元素可以传递!
infFib = [0,1] ++ infFib' 0 1 where
infFib' secondToLast last = [newPart] ++ infFib' last newPart
where newPart = secondToLast + last
我会花一点时间考虑如何清理一些。我不确定它是否超级明显,但是使代码更清晰的一件事就是将每个迭代中产生的元素infFib'
向左移动两个 - 而不是在第一次调用时产生斐波那契序列的第三个元素,在第一次调用时产生第一个元素。这将消除对额外[0,1] ++
的需要以及对特殊where块的需求。所以:
infFib = infFib' 0 1 where
infFib' old new = [old] ++ infFib' new (old+new)
最后一个:将[x] ++ y
的定义展开到x:y
是非常惯用的。这给了我们最终的定义
infFib = infFib' 0 1 where
infFib' old new = old : infFib' new (old+new)
此时我会满意并转到我需要编写的下一个函数。