【问题标题】:Fibonacci sequence generation斐波那契数列生成
【发布时间】:2015-04-25 10:41:55
【问题描述】:

我正在编写一个斐波那契序列生成器,我试图理解 Haskell 中的以下代码

fibs = 1 : 1 : zipWith (+) fibs (tail fibs)

我了解zipWith 是什么,但我不完全了解程序是如何执行的,以及为什么它会生成所有斐波那契数。我试图理解为什么它不会在函数式语言中使用环境概念终止,如下所示:

最初,因为 Haskell 的惰性求值,env 中的绑定应该是 fibs : [1,1,x],然后要求值 fibs,解释器求值 x,在这种情况下是 zipWith (+) fibs (tail fibs)。在评估zipWith 时,它得到fibs : [1,1,2,x],这也是因为Haskell 的懒惰评估。而env中的fibs此时绑定到[1,1,2,x]。然而,为了全面评估fibs,它会继续评估x,然后我们会回到前面的步骤。

这对吗?

另外,我注意到当我在ghci 中运行上面的程序时,它会立即提示它当前计算的斐波那契数列,为什么?完成所有计算后不应该打印结果吗?

【问题讨论】:

  • See one of my answers here 从“懒惰”的角度解释了这种计算的工作原理。这样做的副作用之一是,当您在 GHCi 中评估它时,它实际上被传递给 print,它可以懒惰地使用列表,将每个元素打印为可用的。您定义的fibs 列表实际上是无限的,您实际上无法计算整个事物。

标签: haskell fibonacci


【解决方案1】:

所以,你的大部分推理都是正确的。特别是,您正确描述了如何根据旧元素评估列表中的每个新元素。您也正确完全评估fibs需要重复您概述的步骤,实际上会永远循环。

您缺少的关键因素是我们不必全面评估列表。像fibs = ... 这样的绑定只是为表达式分配了一个名称;它不需要评估整个列表。 Haskell 只会评估运行main 所需的列表。因此,例如,如果我们的main

main = print $ fibs !! 100

Haskell 只会计算 fibs 的前 100 个元素(按照您概述的步骤),但不需要更多,也不会永远循环。

此外,即使我们正在评估整个事物(这将永远循环),我们也可以在进行过程中使用我们计算的部分。这正是您在 ghci 中看到 fibs 的值时所发生的情况:它会尽可能多地打印每个正在计算的元素,而不必等到整个列表准备好。

在 GHCi 中看到严格性

您可以使用:sprint 命令查看在ghci 中评估了多少列表,该命令将为尚未评估的部分打印带有_ 的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 示例(带有图表!),并讨论了如何使用这种方法进行一般的记忆和动态编程。

【讨论】:

  • 我不知道:sprint;这是非常有用的,有点可怕。是否有:sprint 的运行时版本,可能是a -> IO Bool 类型?如果是这样,这将对没有副作用的普通评估产生令人不安的影响。
  • @Cirdec:我相信 存在,就像 unsafeCoerceunsafePerformIO 存在一样:它们是抽象的逃生舱口,只有在了解它是如何实际实施的。不过我不确定它是如何工作的。
猜你喜欢
  • 2011-12-18
  • 2017-11-13
  • 1970-01-01
  • 2012-04-26
  • 2011-02-20
  • 2013-02-24
  • 1970-01-01
  • 1970-01-01
  • 2011-04-26
相关资源
最近更新 更多