lazy 'take' 函数如何进一步计算 Scala 流?

How does the lazy 'take' function compute the Scala stream further?

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

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()?

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

您创建了 Stream 对象,并且该对象具有作为 Int 的 head 和作为函数的 tail。 Take 是 Stream 的函数,它将使用 tail 和 head 计算 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)
  )

我认为应该指出的是 Stream 是惰性求值:

The class Stream implements lazy lists where elements are only evaluated when they are needed.

引自scala-lang.org

fibFrom 函数返回一个 Stream,因此我们知道该函数不会什么都不做,即使在被调用时也是如此;只有当您尝试访问 Stream.
时,它才会开始计算数字 take 函数也 returns 一个 Stream 并且行为懒惰。
print 函数是实际调用递归并在用 15 个数字填充输出时停止的函数(如您的示例)。

您可以通过一项一项地执行函数并查看输出来轻松检查这一点。
只让 运行 fibFrom:

我们可以看到返回值是一个Stream,数字还没有计算出来。

现在,让我们看看 take(15) 做了什么:

与我们的第一个测试相同。

最终,执行 print 给了我们想要的结果,因此实际上 运行ning fibFrom 递归直到达到 15 个数字:

奖励:将流转换为任何非惰性数据结构将触发计算: