连接两个流时,`++` 和 `#:::` 在行为上的确切区别是什么?
What is the exact difference in behavior between `++` and `#:::` when you concatenate two streams?
更具体地说,我有一个类似于下面的代码(你们中的一些人可能会认出 coursera 函数式编程课程的最后一个作业的模式),但未能找到 [=12] 的解决方案=] 但与 #:::
一起工作(alreadySeen 解决方案集包含一个尚未探索的节点,并阻止求解器到达目标位置)。我伪装了代码以遵守协议,在此处的简短示例中 ++
导致 WhosebugError 而 #:::
产生了好的解决方案。 ++
的 scala 文档警告与其他集合一起使用时潜在的无限实现,但在我的示例中它在两个流之间使用。
case class PuzzleNode(pos: Position, moves: List[Move])
def reachableNodes(node: PuzzleNode): Stream[PuzzleNode] = ...
def from(initial: Stream[PuzzleNode], alreadySeen: Set[Position]): Stream[PuzzleNode] = {
if (initial.isEmpty) Stream.empty
else {
val next = for {
startNode <- initial
node <- reachableNodes(startNode)
if(!alreadySeen.contains(node.pos))
} yield node
//initial #::: from(next, alreadySeen ++ next.map(_.pos))
initial ++ from(next, alreadySeen ++ next.map(_.pos))
}
}
编辑:
++
对以下代码表现出惰性行为,考虑到流 class 中的 ++
实现,我可以理解它使用 cons 来构建结果流。
val fibs: Stream[BigInt] = BigInt(0) #:: BigInt(1) #:: fibs.zip(fibs.tail).map { n => n._1 + n._2 }
val prefix: Stream[BigInt] = BigInt(4) #:: BigInt(8) #:: Stream.Empty
(prefix ++ fibs) take 10 foreach println
为什么上述 from
函数的行为不同?
++
立即评估其参数(按值调用),强制对 from
的递归调用进行评估,从而导致 Whosebug
。 #:::
延迟评估参数(按名称调用),因此 from
递归调用将在流被强制时发生。
您的编辑
++
没有表现出惰性行为,您使用已经定义的 Stream
fibs
调用它,它只计算头部。它没有溢出的事实是因为尾部被懒惰地评估了。
要查看差异,请尝试以下操作:
val prefix: Stream[BigInt] = BigInt(4) #:: BigInt(8) #:: Stream.Empty
val ss1 = prefix ++ {println("hello"); BigInt(4)} #:: BigInt(8) #:: Stream.Empty //will print 'hello' since the parameter to ++ is evaluated right away
val ss2 = prefix #::: {println("hello"); BigInt(4)} #:: BigInt(8) #:: Stream.Empty //prints nothing, will print 'hello' when you reach the 3 component of the Stream
ss2(1) //8, nothing printed
ss2(2) //prints 'hello', 4
ss2(2) //nothing printed since the Stream memoizes the value, 4
更具体地说,我有一个类似于下面的代码(你们中的一些人可能会认出 coursera 函数式编程课程的最后一个作业的模式),但未能找到 [=12] 的解决方案=] 但与 #:::
一起工作(alreadySeen 解决方案集包含一个尚未探索的节点,并阻止求解器到达目标位置)。我伪装了代码以遵守协议,在此处的简短示例中 ++
导致 WhosebugError 而 #:::
产生了好的解决方案。 ++
的 scala 文档警告与其他集合一起使用时潜在的无限实现,但在我的示例中它在两个流之间使用。
case class PuzzleNode(pos: Position, moves: List[Move])
def reachableNodes(node: PuzzleNode): Stream[PuzzleNode] = ...
def from(initial: Stream[PuzzleNode], alreadySeen: Set[Position]): Stream[PuzzleNode] = {
if (initial.isEmpty) Stream.empty
else {
val next = for {
startNode <- initial
node <- reachableNodes(startNode)
if(!alreadySeen.contains(node.pos))
} yield node
//initial #::: from(next, alreadySeen ++ next.map(_.pos))
initial ++ from(next, alreadySeen ++ next.map(_.pos))
}
}
编辑:
++
对以下代码表现出惰性行为,考虑到流 class 中的 ++
实现,我可以理解它使用 cons 来构建结果流。
val fibs: Stream[BigInt] = BigInt(0) #:: BigInt(1) #:: fibs.zip(fibs.tail).map { n => n._1 + n._2 }
val prefix: Stream[BigInt] = BigInt(4) #:: BigInt(8) #:: Stream.Empty
(prefix ++ fibs) take 10 foreach println
为什么上述 from
函数的行为不同?
++
立即评估其参数(按值调用),强制对 from
的递归调用进行评估,从而导致 Whosebug
。 #:::
延迟评估参数(按名称调用),因此 from
递归调用将在流被强制时发生。
您的编辑
++
没有表现出惰性行为,您使用已经定义的 Stream
fibs
调用它,它只计算头部。它没有溢出的事实是因为尾部被懒惰地评估了。
要查看差异,请尝试以下操作:
val prefix: Stream[BigInt] = BigInt(4) #:: BigInt(8) #:: Stream.Empty
val ss1 = prefix ++ {println("hello"); BigInt(4)} #:: BigInt(8) #:: Stream.Empty //will print 'hello' since the parameter to ++ is evaluated right away
val ss2 = prefix #::: {println("hello"); BigInt(4)} #:: BigInt(8) #:: Stream.Empty //prints nothing, will print 'hello' when you reach the 3 component of the Stream
ss2(1) //8, nothing printed
ss2(2) //prints 'hello', 4
ss2(2) //nothing printed since the Stream memoizes the value, 4