为什么Stream.foldLeft在运行之前会占用两个元素?

时间:2015-07-31 01:30:42

标签: scala

foldLeft在操作之前只需要集合中的一个元素。那么为什么要尝试解决其中两个呢?难道不是有点懒散吗?

def stream(i: Int): Stream[Int] = 
  if (i < 100) {
    println("taking")
    i #:: stream(i + 1)
  } else Stream.empty

scala> stream(97).foldLeft(0) { case (acc, i) => 
  println("using")
  acc + i
}

taking
taking
using
taking
using
using
res0: Int = 294

我问这个是因为我在可变优先级队列周围构建了一个流,其中折叠的迭代可以将新成员注入流中。它从一个值开始,在第一次迭代期间注入更多值。但是从未见过其他值,因为在第一次迭代之前,流已经在位置2中被解析为empty

1 个答案:

答案 0 :(得分:3)

只能解释为什么会这样。 Here是流#::Cons)的来源:

final class Cons[+A](hd: A, tl: => Stream[A]) extends Stream[A] {
    override def isEmpty = false
    override def head = hd
    @volatile private[this] var tlVal: Stream[A] = _
    @volatile private[this] var tlGen = tl _
    def tailDefined: Boolean = tlGen eq null
    override def tail: Stream[A] = {
      if (!tailDefined)
        synchronized {
          if (!tailDefined) {
            tlVal = tlGen()
            tlGen = null
          }
        }

      tlVal
    }
  }

因此,您可以看到始终计算head(不是懒惰)。这是foldLeft

override final def foldLeft[B](z: B)(op: (B, A) => B): B = {
  if (this.isEmpty) z
  else tail.foldLeft(op(z, head))(op)
}

你可以看到这里调用tail,这意味着“尾部头部”(第二个元素)会自动计算(因为它需要再次调用stream函数来生成尾部) 。所以更好的问题不是“为什么第二” - 问题是为什么Stream总是计算其第一个元素。我不知道答案,但相信scala-library的实现可以通过在lazy内设置Cons来改进,因此您可以通过someLazyCalculation #:: stream(i + 1)

请注意,您的stream函数将被调用两次,但第二种方法为您提供了一种方法,通过提供一些惰性值作为头来避免自动第二个头的计算。像这样的Smthng可以工作(现在它没有):

def stream(i: Int): Stream[Int] = 
  if (i < 100) {
    lazy val ii = {
      println("taking")
      i
    }
    ii #:: stream(i + 1)
  } else Stream.empty

P.S。围绕可变的集合构建(最终)不可变集合可能不是一个好主意。