Streams
естественно стараются избегать создания их элементов несколько раз, если это возможно, memoizing результаты. Из docs:
Stream
класса также использует мемоизация таким образом, что ранее вычисленные значения преобразуются из потока элементов до конкретных значений типа A
.
Мы можем видеть, что по построению Stream
, который печатает каждый раз, когда производится элемент, и запуск нескольких операций:
val stream = Stream.from(0).map(x => { println(x); x }).take(10) //prints 0
val double = stream.map(_ * 2).take(5).toList //prints 1 through 4
val sum = stream.sum //prints 5 through 9
val sum2 = stream.sum //doesn't print any more
Это работает до тех пор, пока вы используете val
и не def
:
Пока что что-то держится за голову, голова держится за хвост и поэтому продолжает рекурсивно. Если, с другой стороны, ничего не держится на голове (например, мы использовали def
для определения Stream
), а затем, когда он больше не используется напрямую, он исчезает.
Это означает, что запоминание нужно быть осторожными с Streams
:
Нужен быть осторожным запоминание; вы можете очень быстро съесть большой объем памяти, если не будете осторожны. Причина этого в том, что memoization Stream
создает структуру, похожую на scala.collection.immutable.List
.
Конечно, если генерирующий из пунктов не то, что стоит дорого, но фактического обходом Stream
или запоминание не доступно, потому что это было бы слишком дорого, всегда можно использовать foldLeft
с кортеж, отслеживание нескольких значений:
//Only prints 0-9 once, even if stream is a def
val (sum, double) = stream.foldLeft(0 -> List.empty[Int]) {
case ((sum, list), next) => (sum + next, list :+ (next * 2))
}
Если это достаточно распространенная операция, вы можете даже обогащать Stream
, чтобы сделать некоторые из наиболее распространенных операций, таких как foldLeft
, reduceLeft
и других доступных в этом формате:
implicit class RichStream[T](val stream: Stream[T]) extends AnyVal {
def doubleFoldLeft[A, B](start1: A, start2: B)(f: (A, T) => A, g: (B, T) => B) = stream.foldLeft(start1 -> start2) {
case ((aAcc, bAcc), next) => (f(aAcc, next), g(bAcc, next))
}
}
Который позволит вам делать такие вещи, как:
val (sum, double) = stream.doubleFoldLeft(0, List.empty[Int])(_ + _, _ :+ _)
Спасибо. Я не понимал, что потоки держатся в голове. DoubleFoldLeft опрятен, но в моем конкретном случае это осложнится. Будет множество * вычислений, которые мне нужно объединить. Это может стать громоздким без сильной абстракции. –
Кроме того, когда я пытался использовать foldLeft, отслеживая несколько значений, я кое-что понял: я потерял преимущества функций более высокого порядка. Другими словами, хотя я мог бы обычно писать что-то вроде myStream.groupBy (identity) .mapValues (_. Size) для создания карты подсчетов, написание foldLeft с кортежем означало, что я должен был следить за увеличением карты. Поэтому я опускался на более низкий уровень абстракции, и я хочу иметь свой функциональный пирог и съесть его! :) –
Вы определенно теряете часть своих функций более высокого порядка здесь, но это естественно, когда требуется высокая эффективность или очень конкретные оптимизации. Вы уверены, что вам нужен этот уровень оптимизации? Достаточно ли естественной мемуары «Потока»? Рассматривали ли вы использование конструкции нижнего уровня, как понимание 'for'? –