Подтвердить что ты не робот

Scala: Потоки не действуют лениво?

Я знаю, что потоки должны быть лениво оценены последовательностями в Scala, но я думаю, что я страдаю от какого-то фундаментального недоразумения, потому что они кажутся более нетерпеливыми, чем я ожидал.

В этом примере:

 val initial = Stream(1)
 lazy val bad = Stream(1/0)
 println((initial ++ bad) take 1)

Я получаю a java.lang.ArithmeticException, который, по-видимому, вызывает нулевое деление. Я ожидал бы, что bad никогда не будет оценен, так как я попросил только один элемент из потока. Что не так?

4b9b3361

Ответ 1

Хорошо, поэтому, комментируя другие ответы, я подумал, что мог бы также включить мои комментарии в правильный ответ.

Потоки действительно ленивы и будут только вычислять свои элементы по требованию (и вы можете использовать #:: для создания элемента потока по элементу, как :: для List). Например, следующее исключение не будет вызывать:

(1/2) #:: (1/0) #:: Stream.empty

Это связано с тем, что при применении #:: хвост передается по имени, чтобы не оценивать его с нетерпением, но только при необходимости (см. ConsWrapper.# ::, const.apply и class Cons в Stream.scala для более Детали). С другой стороны, голова передается по значению, а это означает, что ее всегда будут оценивать, независимо от того, что (как упоминалось в Senthil). Это означает, что выполнение следующего действия фактически вызовет исключение ArithmeticException:

(1/0) #:: Stream.empty

Это важно знать о потоках. Однако это не проблема, с которой вы сталкиваетесь.

В вашем случае арифметическое исключение происходит до создания экземпляра только одного потока. При вызове Stream.apply в lazy val bad = Stream(1/0) аргумент выполняется с нетерпением, потому что он не объявлен как параметр по имени. Stream.apply фактически принимает параметр vararg, и они обязательно передаются по значению. И даже если он был отправлен по имени, ArithmeticException будет запущен вскоре после этого, потому что, как было сказано ранее, глава Stream всегда оценивается раньше.

Ответ 2

Тот факт, что потоки ленивы, не меняет того факта, что аргументы метода оцениваются с нетерпением.

Stream(1/0) расширяется до Stream.apply(1/0). Семантика языка требует, чтобы аргументы были оценены до вызова метода (поскольку метод Stream.apply не использует аргументы по вызову), поэтому он пытается оценить 1/0 для передачи в качестве аргумента Stream.apply, который вызывает ваше исключение ArithmeticException.

Есть несколько способов, которыми вы можете это работать. Поскольку вы уже объявили bad как lazy val, проще всего использовать также lazy #::: оператор конкатенации потока, чтобы избежать принудительной оценки:

val initial = Stream(1)
lazy val bad = Stream(1/0)
println((initial #::: bad) take 1)
// => Stream(1, ?)

Ответ 3

Поток будет оценивать голову, а оставшийся хвост оценивается лениво. В вашем примере оба потока имеют только голову и, следовательно, дают ошибку.