我正在写一个斐波那契数列生成器,我试图在 Haskell 中理解以下代码fibs = 1 : 1 : zipWith (+) fibs (tail fibs)
我明白 zipWith
是什么,但我不完全知道程序是如何执行的,以及它为什么会生成所有的斐波那契数。我试图理解为什么它不使用函数式语言中的环境概念终止,如下所示:
最初,因为 Haskell 的惰性求值, env
中的绑定(bind)应该是 fibs : [1,1,x]
,然后为了求值 fibs
,解释器求值 x
,在这种情况下是 zipWith (+) fibs (tail fibs)
。在评估 zipWith
时,它会得到 fibs : [1,1,2,x]
,同样是因为 Haskell 的惰性评估。而fibs
中的env
此时绑定(bind)了[1,1,2,x]
。然而,为了全面评估 fibs
,它会继续评估 x
,我们回到前面的步骤。
这样对吗?
此外,我注意到当我在 ghci
中运行上面的程序时,它会立即提示它当前计算的斐波那契数列,为什么?一旦完成所有计算,它不应该打印结果吗?
最佳答案
所以,你的大部分推理是正确的。特别是,您正确地描述了如何根据旧元素评估列表中的每个新元素。您也是正确的,要全面评估 fibs
需要重复您概述的步骤,并且实际上会永远循环。
您缺少的关键要素是 我们不必完全评估列表 。像 fibs = ...
这样的绑定(bind)只是为表达式分配一个名称;它不需要评估整个列表。 Haskell 只会评估运行 main
所需的列表。因此,例如,如果我们的 main
是
main = print $ fibs !! 100
Haskell 只会计算
fibs
的前 100 个元素(按照您概述的步骤),但不需要更多元素,并且不会永远循环。此外,即使我们正在评估整个事情(将永远循环),我们也可以在进行过程中使用我们计算过的部分。当您在 ghci 中看到
fibs
的值时,这正是发生的情况:它在计算每个元素时尽可能多地打印,而不必等到整个列表准备就绪。在 GHCi 中看到严格
您可以使用
ghci
命令查看在 :sprint
中评估了多少列表,该命令将为尚未评估的部分(称为“thunk”)打印带有 _
的 Haskell 数据结构。您可以使用它来查看 fibs
如何在操作中得到评估:Prelude> let fibs = 1 : 1 : zipWith (+) fibs (tail fibs)
Prelude> :sprint fibs
fibs = _
Prelude> print $ fibs !! 10
89
Prelude> :sprint fibs
fibs = _
哎呀,这不是我们所期望的!事实上,这是一个缺乏单态限制是一个问题的情况!
fibs
获取多态类型Prelude> :t fibs
fibs :: Num a => [a]
这意味着每次使用它时它的行为就像一个函数调用,而不是一个正常的值。 (在后台,GHC 将
Num
类型类实例化为将字典传递给 fibs
;它的实现就像一个 NumDictionary a -> [a]
函数。)为了真正理解发生了什么,我们需要显式地使
fibs
单态。我们可以通过从限制处于事件状态的模块加载它或给它一个明确的类型签名来做到这一点。让我们做后者:Prelude> let fibs :: [Integer]; fibs = 1 : 1 : zipWith (+) fibs (tail fibs)
Prelude> :sprint fibs
fibs = _
Prelude> print $ fibs !! 10
89
Prelude> :sprint fibs
fibs = 1 : 1 : 2 : 3 : 5 : 8 : 13 : 21 : 34 : 55 : 89 : _
你就是这样:你可以看到列表的哪些部分需要评估,哪些不需要得到第 10 个元素。您可以使用其他列表或其他惰性数据结构来很好地了解后台发生的事情。
此外,您可以查看 my blog post 关于这种懒惰。它更详细地介绍了
fibs
示例(带有图表!),并讨论了如何将这种方法用于一般记忆化和动态编程。关于haskell - 斐波那契数列生成,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/28685119/