我对流很满意,但我承认我对这种行为感到困惑:
import collection.immutable.Stream
object StreamForceTest extends App {
println("Computing fibs")
val fibs: Stream[BigInt] = BigInt(0) #:: BigInt(1) #::
fibs.zip(fibs.tail).map((x: (BigInt, BigInt)) => {
println("Adding " + x._1 + " and " + x._2);
x._1 + x._2
})
println("Taking first 5 elements")
val fibs5 = fibs.take(5)
println("Computing length of that prefix")
println("fibs5.length = " + fibs5.length)
}
带输出
Computing fibs
Taking first 5 elements
Computing length of that prefix
Adding 0 and 1
Adding 1 and 1
Adding 1 and 2
fibs5.length = 5
为什么take(5)
而不是强制计算流的值,而length
是否这样做?实际上谁都不需要看看这些值,但我会认为take
更比CCD_ 4更有可能做到这一点。检查github上的源代码,我们发现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)
)
和length
:
override def length: Int = {
var len = 0
var left = this
while (!left.isEmpty) {
len += 1
left = left.tail
}
len
}
head
和tail
的定义是从子类(Empty
和Cons
)。(当然,Empty
是一个对象,而不是类及其对head
和tail
的定义例外情况。)有一些微妙之处,但它们似乎与制作有关确保CCD_ 15的CCD_;head
这个定义直接来自关于Scala构造函数的第0讲。注意,length
不会在head
附近,但它是进行强制。
所有这些都是关于Scala流的接近程度的普遍困惑的一部分是Haskell列表。我以为哈斯克尔治疗了头部和尾部对称(我不是一个严肃的Haskell黑客),Scala强迫在更多情况下进行头部评估。我在想办法正是这些情况。
Stream的头是严格的,尾是懒惰的,正如您在cons.apply和cons构造函数中看到的那样:
def apply[A](hd: A, tl: => Stream[A]) = new Cons(hd, tl)
class Cons[+A](hd: A, tl: => Stream[A]) extends Stream[A]
请注意take
方法引用tail
:的上下文
cons(head, tail take n-1)
因为表达式tail take n-1
被用作cons
的第二个参数,该参数是按名称传递的,所以它不会强制计算tail take n-1
,因此也不会强制计算tail
。
而在length
中,语句
left = left.tail
,通过将left.tail
分配给var
,确实强制其评估。
Scala是"默认情况下严格的"。在大多数情况下,将对您引用的所有内容进行评估。只有在方法/构造函数参数使用=>
声明按名称调用参数的情况下,我们才会进行延迟求值,在区域性中,除非有特殊原因,否则我们通常不会使用此方法。
让我提供另一个答案,这个答案只是从高层来看,即没有实际考虑代码。
如果你想知道一个流有多长,必须一直评估到最后。否则,你只能猜测它的长度。诚然,你可能并不真正关心价值观(因为你只想计算它们),但这无关紧要。
另一方面,当你从流(或者任何集合)中"获取"一定数量的元素时,你只是说你最多想要这个数量的元素。结果仍然是一个流,即使它可能已经被截断。