scala视图过滤器不偷懒?

scala view filter not lazy?

在尝试理解流、迭代器和集合视图之间的区别时,我偶然发现了以下奇怪的行为。

这里是代码(map 和 filter 简单地打印他们的输入并原样转发):

object ArrayViewTest {
  def main(args: Array[String]) {
    val array = Array.range(1,10)

    print("stream-map-head: ")
    array.toStream.map(x => {print(x); x}).head

    print("\nstream-filter-head: ")
    array.toStream.filter(x => {print(x); true}).head

    print("\niterator-map-head: ")
    array.iterator.map(x => {print(x); x}).take(1).toArray

    print("\niterator-filter-head: ")
    array.iterator.filter(x => {print(x); true}).take(1).toArray

    print("\nview-map-head: ")
    array.view.map(x => {print(x); x}).head

    print("\nview-filter-head: ")
    array.view.filter(x => {print(x); true}).head
  }
}

及其输出:

stream-map-head: 1
stream-filter-head: 1
iterator-map-head: 1
iterator-filter-head: 1
view-map-head: 1
view-filter-head: 123456789    // <------ WHY ?

为什么在视图上调用的过滤器会处理整个数组? 我希望过滤器的评估仅通过调用 head 驱动一次,就像在所有其他情况下一样,特别是就像在视图上使用地图一样。

我缺少哪个洞察力?

(评论的小问题,为什么迭代器上没有头部?)

编辑: scala.collection.mutable.ArraySeq.range(1,10)scala.collection.mutable.ArrayBuffer.range(1,10)scala.collection.mutable.StringBuilder.newBuilder.append("123456789") 实现了相同的奇怪行为(如此处的 scala.Array.range(1,10))。 但是,对于所有其他可变集合和所有不可变集合,视图上的过滤器按预期工作并输出 1.

我认为它必须这样做 Array 是一个可变的索引序列。它的视图也是一个可变集合 :) 因此,当它创建一个视图时,它会创建一个在原始集合和过滤后的集合之间映射的索引。并且懒惰地创建这个索引并没有真正意义,因为当有人请求第 i 个元素时,可能会遍历整个源数组。在调用 head 之前不会创建此索引,从某种意义上说,它仍然是惰性的。这仍然没有在 scala 文档中明确说明,乍一看它像是一个错误。

对于小问题,我认为迭代器上 head 的问题是人们期望 head 是纯函数,即你应该能够调用它 n 次并且它应该return 每次都是一样的结果。而迭代器本质上是可变的数据结构,按照约定只能遍历一次。这可以通过缓存迭代器的第一个元素来克服,但我发现这非常令人困惑。

似乎 head 使用 isEmpty

trait IndexedSeqOptimized[+A, +Repr] extends Any with IndexedSeqLike[A, Repr] { self =>
...
override /*IterableLike*/
def head: A = if (isEmpty) super.head else this(0)

并且isEmpty使用length

trait IndexedSeqOptimized[+A, +Repr] extends Any with IndexedSeqLike[A, Repr] { self =>
  ...
  override /*IterableLike*/
  def isEmpty: Boolean = { length == 0 }

length的实现是从Filtered开始使用的,它循环遍历整个数组

trait Filtered extends super.Filtered with Transformed[A] {
  protected[this] lazy val index = {
    var len = 0
    val arr = new Array[Int](self.length)
    for (i <- 0 until self.length)
      if (pred(self(i))) {
        arr(len) = i
        len += 1
      }
    arr take len
  }
  def length = index.length
  def apply(idx: Int) = self(index(idx))
}

Filtered trait 仅在调用 filter

时使用
protected override def newFiltered(p: A => Boolean): Transformed[A] =
 new { val pred = p } with AbstractTransformed[A] with Filtered

这就是为什么在使用 filter 而不是在使用 map

时会发生这种情况