3

在 Martin Odersky 的“Scala 编程”一书中,有一个计算斐波那契数列的示例,它从作为参数传递给函数 fibFrom 的 2 个数字开始。

def fibFrom(a: Int, b: Int): Stream[Int] =
       a #:: fibFrom(b, a + b)

如果将方法 take() 应用于此递归函数,例如:

fibFrom(1, 1).take(15).print

输出将是:

1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, empty

也许这个输出对于更有经验的人来说是显而易见的,但我不明白这个方法 take() 究竟是如何使流被进一步计算的。15 是否以某种方式不明显地传递到 fibFrom() 中?

4

2 回答 2

3

我认为应该指出的Stream是懒惰地评估

Stream 类实现了惰性列表,其中元素仅在需要时才被评估。

引用自scala-lang.org

fibFromfunction 正在返回 a Stream,所以我们知道该函数不会什么都不做,即使被调用;只有当您尝试访问Stream.
take函数也返回 aStream并懒惰地行动。
print函数是实际调用递归并在用 15 个数字填充输出时停止的函数(如您的示例)。

您可以通过一一执行功能并查看输出来轻松检查这一点。
让我们只运行fibFrom: 我们可以看到返回的值为 a并且尚未计算数字。
在此处输入图像描述
Stream

现在,让我们看看有什么take(15)作用: 与我们的第一个测试相同。
在此处输入图像描述

最终,执行print给了我们想要的结果,因此实际上fibFrom递归地运行直到达到 15 个数字:
在此处输入图像描述

奖励:将 Stream 转换为任何非惰性数据结构将触发计算:
在此处输入图像描述

于 2020-03-27T12:19:38.503 回答
2

a #:: fibFrom(b, a + b)

您创建了 Stream 对象,并且该对象具有 Int 的 head 和函数的 tail。Take 是 Stream 的函数,它将使用尾部和头部计算 15 个元素。您可以查看 take() 函数的源代码:

  override def take(n: Int): Stream[A] = (
    // Note that the n == 1 condition appears redundant but is not.
    // It prevents "tail" from being referenced (and its head being evaluated)
    // when obtaining the last element of the result. Such are the challenges
    // of working with a lazy-but-not-really sequence.
    if (n <= 0 || isEmpty) Stream.empty
    else if (n == 1) cons(head, Stream.empty)
    else cons(head, tail take n-1)
  )
于 2020-03-27T11:52:19.657 回答