我正在写一个斐波那契序列生成器,我试图理解Haskell中的以下代码
fibs = 1 : 1 : zipWith (+) fibs (tail fibs)
我理解zipWith
是什么,但我不完全知道程序是如何执行的,以及为什么它会生成所有的斐波纳契数。我试图理解为什么它不能在函数式语言中使用环境概念终止,如下所示:
最初,因为Haskell的懒惰评估,env
中的绑定应该是fibs : [1,1,x]
,然后评估fibs
,解释器会评估x
在这种情况下zipWith (+) fibs (tail fibs)
。在评估zipWith
时,由于对Haskell的惰性评估,它再次获得fibs : [1,1,2,x]
。此时,fibs
中的env
绑定到[1,1,2,x]
。但是,要全面评估fibs
,我们会继续评估x
,然后我们会回到之前的步骤。
这是对的吗?
此外,我注意到当我在ghci
中运行上面的程序时,它立即提示它当前计算的斐波那契序列,为什么?一旦完成所有计算,它不应该打印结果吗?
答案 0 :(得分:11)
所以,你的大部分推理都是正确的。特别是,您正确地描述了列表中每个新元素如何根据旧元素进行评估。您也是正确的完全评估fibs
需要重复您概述的步骤,并且实际上会永远循环。
您缺少的关键因素是我们不必完全评估列表。像fibs = ...
之类的绑定只为表达式指定一个名称;它不需要评估整个列表。 Haskell只会根据运行main
所需的列表进行评估。因此,例如,如果我们的main
是
main = print $ fibs !! 100
Haskell只计算fibs
的前100个元素(按照你概述的步骤),但不需要更多,不会永远循环。
此外,即使我们 评估整个事物(它将永远循环),我们也可以使用我们计算的部分。这正是当您在ghci中看到fibs
的值时发生的情况:它会在计算每个元素时尽可能多地打印,并且不必等到整个列表准备就绪。
您可以使用ghci
命令在:sprint
中查看列表的评估范围,该命令将打印带有_
的Haskell数据结构,用于尚未评估的部分(称为“thunks”)。您可以使用它来查看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
示例(带图表!),并讨论了如何将此方法用于一般记忆和动态编程。