scala中stream的构造困扰了自己好久,programming in scala中介绍stream的篇幅很少,且例子容易理解,

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

// List[Int] = List(1, 1, 2, 3, 5, 8, 13, 21, 34, 55)
fibFrom(1, 1).take(10).toList

stream是一种lazy的容器,故而上面fibFrom函数的递归定义很容易理解,但同样计算fibonacci数列,在stream的文档上出现了另一个例子,

val fibs: Stream[Int] = 1 #:: 1 #:: fibs.zip(fibs.tail).map { n => n._1 + n._2 }
// List[Int] = List(1, 1, 2, 3, 5, 8, 13, 21, 34, 55)
fibs.take(10).toList

这个例子中一直让我难以理解的是,

fibs.zip(fibs.tail).map { n => n._1 + n._2 }

// 增加print注释
fibs.zip(fibs.tail).map { n => {println(n); n._1 + n._2} }

// 输出(1, 1)
fibs.take(3).toList
// 输出(1, 2)
fibs.take(4).toList
// 输出(2, 3)
fibs.take(5).toList

一步步看下来可以发现每一次迭代map中的计算只进行了一次,也就是说fibs指向的长度一直为2。从fibs、fibs.tail的值来看,每次都是取已计算出的stream的最后两个元素。

为什么

最具体的原因只能去看stream的源码实现了,但从另一个更简单的例子可以看出上述推断是正确的,

val nature:Stream[Int] = 1 #:: nature.map(_ + 1)
// List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
nature.take(10).toList

val doubleNature:Stream[Int] = 1 #:: 1 #:: doubleNature.map(_ + 1)
// List[Int] = List(1, 1, 2, 2, 3, 3, 4, 4, 5, 5)
doubleNature.take(10).toList

val tripleNature:Stream[Int] = 1 #:: 1 #:: 1 #:: tripleNature.map(_ + 1)
// List[Int] = List(1, 1, 1, 2, 2, 2, 3, 3, 3, 4)
tripleNature.take(10).toList

上面的片段排除了stream.tail的干扰,可以看到在迭代过程中stream总是指向最新几个数目的元素,且元素数目等同于初始stream中确定的元素数。