Варианты использования для потоков в Scala - PullRequest
50 голосов
/ 19 января 2010

В Scala есть класс Stream, который очень похож на итератор. Тема Разница между Iterator и Stream в Scala? дает некоторое представление о сходстве и различиях между ними.

Понять, как использовать поток, довольно просто, но у меня не так много распространенных вариантов использования, где я бы использовал поток вместо других артефактов.

Идеи, которые у меня сейчас есть:

  • Если вам нужно использовать бесконечный ряд. Но это не похоже на общий случай использования, поэтому не соответствует моим критериям. (Пожалуйста, поправьте меня, если это распространено, и у меня просто слепое пятно)
  • Если у вас есть ряд данных, где каждый элемент должен быть вычислен, но вы, возможно, захотите использовать его несколько раз. Это слабо, потому что я мог бы просто загрузить его в список, который концептуально проще для большого подмножества разработчиков.
  • Возможно, существует большой набор данных или вычислительно дорогостоящий ряд, и существует высокая вероятность того, что необходимые вам элементы не потребуют посещения всех элементов. Но в этом случае итератор будет хорошим совпадением, если вам не нужно выполнять несколько поисков, в этом случае вы также можете использовать список, даже если он будет несколько менее эффективным.
  • Существует сложный ряд данных, которые необходимо использовать повторно. Снова список может быть использован здесь. Хотя в этом случае оба случая будут одинаково сложны в использовании, и поток будет более подходящим, поскольку не все элементы должны быть загружены. Но опять же не так часто ... или это?

Так я пропустил какое-нибудь большое использование? Или это предпочтение разработчика по большей части?

Спасибо

Ответы [ 4 ]

40 голосов
/ 20 января 2010

Основное различие между Stream и Iterator заключается в том, что последний является изменяемым и, так сказать, «одноразовым», тогда как первый - нет. Iterator имеет лучший объем памяти, чем Stream, но тот факт, что он является изменяемым, может быть неудобным.

Возьмем этот классический генератор простых чисел, например:

def primeStream(s: Stream[Int]): Stream[Int] =
  Stream.cons(s.head, primeStream(s.tail filter { _ % s.head != 0 }))
val primes = primeStream(Stream.from(2))

Его также можно легко записать с помощью Iterator, но Iterator не будет сохранять простых чисел, вычисленных до сих пор.

Итак, одним важным аспектом Stream является то, что вы можете передавать его другим функциям, не дублируя его вначале или не генерируя его снова и снова.

Что касается дорогих вычислений / бесконечных списков, эти вещи можно сделать также с помощью Iterator. Бесконечные списки на самом деле весьма полезны - вы просто не знаете об этом, потому что у вас их нет, поэтому вы видели алгоритмы, которые являются более сложными, чем строго необходимыми только для работы с принудительными конечными размерами.

18 голосов
/ 20 января 2010

В дополнение к ответу Даниэля, имейте в виду, что Stream полезен для кратковременных оценок. Например, предположим, у меня есть огромный набор функций, которые принимают String и возвращают Option[String], и я хочу продолжать выполнять их, пока одна из них не заработает:

val stringOps = List(
  (s:String) => if (s.length>10) Some(s.length.toString) else None ,
  (s:String) => if (s.length==0) Some("empty") else None ,
  (s:String) => if (s.indexOf(" ")>=0) Some(s.trim) else None
);

Ну, я определенно не хочу выполнять весь список, и на List нет никакого удобного метода, который говорит: "обрабатывать их как функции и выполнять до тех пор, пока одна из них не будет выполнена возвращает что-то отличное от None ". Что делать? Возможно это:

def transform(input: String, ops: List[String=>Option[String]]) = {
  ops.toStream.map( _(input) ).find(_ isDefined).getOrElse(None)
}

Это берет список и обрабатывает его как Stream (который на самом деле ничего не оценивает), затем определяет новый Stream, который является результатом применения функций (но он еще ничего не оценивает) ), затем ищет первый, который определен - и здесь, волшебным образом, он оглядывается назад и понимает, что должен применить карту и получить правильные данные из исходного списка - и затем разворачивает его из Option[Option[String]] в Option[String] с использованием getOrElse.

Вот пример:

scala> transform("This is a really long string",stringOps)
res0: Option[String] = Some(28)

scala> transform("",stringOps)
res1: Option[String] = Some(empty)

scala> transform("  hi ",stringOps)
res2: Option[String] = Some(hi)

scala> transform("no-match",stringOps)
res3: Option[String] = None

Но это работает? Если мы добавим println в наши функции, чтобы мы могли определить, вызваны ли они, мы получим

val stringOps = List(
  (s:String) => {println("1"); if (s.length>10) Some(s.length.toString) else None },
  (s:String) => {println("2"); if (s.length==0) Some("empty") else None },
  (s:String) => {println("3"); if (s.indexOf(" ")>=0) Some(s.trim) else None }
);
// (transform is the same)

scala> transform("This is a really long string",stringOps)
1
res0: Option[String] = Some(28)

scala> transform("no-match",stringOps)                    
1
2
3
res1: Option[String] = None

(Это с Scala 2.8; реализация 2.7, к сожалению, иногда будет сбрасываться на единицу. И обратите внимание, что do накапливает длинный список None по мере накопления ошибок, но, вероятно, это Недорого по сравнению с вашими настоящими вычислениями.)

7 голосов
/ 18 июля 2011

Я мог бы представить, что если вы опрашиваете какое-то устройство в режиме реального времени, Stream будет более удобным.

Подумайте о GPS-трекере, который возвращает фактическое положение, если вы спросите его.Вы не можете заранее вычислить место, где вы будете через 5 минут.Вы можете использовать его в течение нескольких минут только для актуализации пути в OpenStreetMap, или вы можете использовать его для экспедиции в течение шести месяцев в пустыне или тропическом лесу.

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

3 голосов
/ 20 января 2010

Stream до Iterator, как immutable.List до mutable.List. Любимая неизменность предотвращает класс ошибок, иногда за счет производительности.

Скаляр сам по себе не застрахован от этих проблем: http://article.gmane.org/gmane.comp.lang.scala.internals/2831

Как указывает Даниэль, отдавая предпочтение лени, а не строгости, можно упростить алгоритмы и упростить их составление.

...