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
时会发生这种情况
在尝试理解流、迭代器和集合视图之间的区别时,我偶然发现了以下奇怪的行为。
这里是代码(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