懒惰评估斐波那契函数

时间:2018-04-27 11:51:59

标签: haskell recursion fibonacci

我在Haskell中编写了下面的函数,我不太清楚为什么它不能用于惰性求值。

infFib :: [Integer]
infFib = infFib' []
    where 
        infFib' [] = infFib' [0,1]
        infFib' (xs) = infFib' (xs ++ [(foldr (\a b -> a+b) 0 (take 2 (reverse xs)))])

1 个答案:

答案 0 :(得分:5)

tl; dr因为infFib'永远不会返回,它只会继续拨打电话。

我希望您在构建infFib'时的想法大致如此:

  • 我想要一个无限的名单。
  • 我知道列表是如何开始的,列表应该如何增长。
  • 我会编写一个函数来稍微增加列表,然后迭代它。
  • 哎呀,Haskell不进行迭代 - 它会进行递归。好的,我会编写一个函数来增加列表的一点点,并用它递归。

不幸的是,你编写它的方式,你用一个更长更长的列表来称呼你自己 - 但你永远不会将那个列表(甚至部分)返回给调用者!您只需继续构建更长更长的列表并在其上调用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)

此时我会满意并转到我需要编写的下一个函数。