Scala map on iterator does not produce side effect

2020-03-21 10:05发布

问题:

Why is it that,

scala> List(1,2,3,4).iterator.map((x: Int) => println(x))

does not print out

1
2
3
4

while

List(1,2,3,4).map((x: Int) => println(x))
List(1,2,3,4).foreach((x: Int) => println(x))
List(1,2,3,4).iterator.foreach((x: Int) => println(x))

all do?

In other words, why is it that a map on a iterator that maps type T to Unit and has side effects unable to show those side effects?

Edit:

Also why does the following invocation of lazyMap actually computes the new iterator (provide the complete new iterator) from beginning to end if iterator is lazy?

def lazyMap[T, U](coll: Iterable[T], f: T => U) = new Iterable[U] {
  def iterator = coll.iterator map f
}

scala> lazyMap(List(1,2,3,4), (x: Int) => x + 1)
res4: java.lang.Object with Iterable[Int] = (2, 3, 4, 5)

回答1:

Cause map on iterator is lazy and you need some strictness:

scala> List(1,2,3,4).iterator.map((x: Int) => println(x))
res0: Iterator[Unit] = non-empty iterator

// nothing actually happened yet, just remember to do this printing things

scala> res0.toList
1
2
3
4
res1: List[Unit] = List((), (), (), ())

When you doing foreach on iterator it is quite obvious that you're doing side effects, so lazyness will be undesired. I wouldn't said so about map.

UPD

As for your edit: the reason for such behaviour, is that there is implicit call of toString for statement result which in turn stricts the iterator -- try this code on your own:

scala> { lazyMap(List(1,2,3,4), {(x: Int) => println(x); x + 1}); 1 }

and you'll see that function f is never called



回答2:

The point of an Iterator is laziness. In other words, when you create an Iterator, it will not evaluate anything until you go to read the data. Here's what that looks like:

scala> val itr = List(1,2,3).iterator
itr: Iterator[Int] = non-empty iterator

Ok, we've got an iterator now. But it hasn't actually looked at the list yet.

scala> val mappedItr = itr.map((x: Int) => println(x))
mappedItr: Iterator[Unit] = non-empty iterator

Now we have a new Iterator. This one will, when data is accessed, apply the function that has been mapped. But we still haven't actually looked at the original list.

scala> mappedItr.next
1

This is the first time we have accessed data, so it is the first time that the Iterator has looked into the list. We called next, so we got the first element. Since our iterator has a map queued up, it applies the mapped function when we access that element. So we see the result of the function applied to the next item.

We can do it again to get the next element:

scala> mappedItr.next
2

And, again, it evaluates the function only when it needs to, in order to give us the final result.