【发布时间】:2016-07-17 23:37:15
【问题描述】:
我对流很满意,但我承认我对这种行为感到困惑:
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 更多
可能比length 做到这一点。检查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 的定义只是 throw
例外。)有一些微妙之处,但它们似乎与制作有关
确保 Cons 的 tail 被延迟评估; head
定义直接来自关于 Scala 构造函数的第 0 课。
请注意,length 不会靠近 head,但它是
做强迫症。
所有这些都是关于 Scala 流有多接近的普遍困惑的一部分 是 Haskell 列表。我以为 Haskell 对待头部和尾部 对称(我不是一个认真的 Haskell 黑客),Scala 强制 在更多情况下进行头部评估。我想弄清楚 这些情况究竟是什么。
【问题讨论】:
-
我对流不满意,但对于迭代器,我希望 take 变得懒惰。但我习惯了
iterator.size耗尽迭代器只是为了检查长度。是否存在其他一些上下文或语言,其中 take 意味着力量?只是好奇。我可以理解动词 take 听起来像是强制一个值。 -
我不确定你有什么困惑(特别是,如果你真的“对流很满意”):
.take(5)只需要扫描流的前五个成员来计算结果。.length需要一直到最后一个元素来计算它们。真的,我只是看不出这有什么令人费解的地方。 -
@Dima 我不明白。 take 什么都不做,因为它仍然是惰性的,但长度必须强制。那么
needs to scan through the first five members是什么意思呢?另外,if you are really "comfortable with the streams"?例如,当我说“我对流媒体不满意”时,我打算使用“自嘲式幽默”,我希望它不会被视为讽刺。无论如何,我们的自我理解就是这样。 -
@Dima 谢谢;是的,当然我应该意识到
take中的递归将被暂停,因为递归调用在cons内。但是如果你递归地写length:if (s.isEmpty) 0 else 1 + tail.length,就会发现不对称。如果不强迫头部,您将无法访问尾部。它没有必须那样。 -
@airfoyle 恐怕我不明白你在说什么。也许,这对我来说太计算机科学了 :) 我的意思是,我不确定你在说什么“不对称”,但它不清楚,即使对实现一无所知,为了告诉流的长度,你必须扫描到最后,并设置一个限制,你可以遍历它......好吧,不需要做任何事情吗? :) 或者你的问题是关于实现的,因为你对 cons 的 pass-by-name 参数感到困惑?
标签: scala stream lazy-evaluation