Почему мой takeWhile не работает с моим потоком

Следующий код печатает "* 1". Что за мистификация, если я удаляю комментарий, он возвращает "* 4", что я ожидал

var max = 0
lazy val list: Stream[Int] = 1 #:: Stream.from(2)
list.takeWhile {
  x =>
    max = x
    x < 4
}//.foreach(println)
println("*" + max)

Ответ 1

Прежде всего: lazy во второй строке ничего не делает - вы можете удалить его и получить тот же результат.

Что еще более важно: takeWhile на самом деле ленив, потому что он просто возвращает другой Stream, и ничто мимо заголовка этого потока не будет оценено до его появления. Рассмотрим следующее:

val s = Stream.from(1).takeWhile(_ > 0)

Вы и я знаем, что s будет бесконечным потоком, но если мы запустим REPL и наберем это, он с удовольствием оценит его:

scala> val s = Stream.from(1).takeWhile(_ > 0)
s: scala.collection.immutable.Stream[Int] = Stream(1, ?)

То же самое происходит в вашем примере: (Int) ⇒ Boolean, который вы передали в takeWhile, не будет получать какие-либо элементы за пределами заголовка потока, пока что-то вроде вашего foreach не сделает это необходимо.

Вы можете увидеть это еще более резко, добавив что-то вроде println внутри предиката takeWhile:

scala> val s = Stream.from(1).takeWhile { x => println("Checking: " + x); x < 4 }
Checking: 1
s: scala.collection.immutable.Stream[Int] = Stream(1, ?)

scala> val l = s.toList
Checking: 2
Checking: 3
Checking: 4
l: List[Int] = List(1, 2, 3)

Ясно, что предикат только вызывается для заголовка потока, пока мы не заставим оценку остальной части потока, вызвав toList.