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

Семантика Scala Traversable, Iterable, Sequence, Stream и View?

Есть и другие вопросы, такие как Scala: В чем разница между Traversable и Iterable чертами в коллекциях Scala? и Как получить сумму квадратов из двух списков в Scala?, которая частично ответит на вопрос. Я чувствовал, что вопрос, который охватывает все это в одном месте, имеет смысл.

4b9b3361

Ответ 1

Traversable - это вершина иерархии коллекций. Его основным методом является "foreach", поэтому он позволяет делать что-то для каждого элемента коллекции.

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

Seq (uence) является Iterable, где порядок элементов фиксирован. Поэтому имеет смысл говорить об индексе элемента.

Потоки - это ленивые последовательности. То есть элементы потока не могут быть вычислены до того, как они будут доступны. Это позволяет работать с бесконечными последовательностями, такими как последовательность всех целых чисел.

Представления - это нестрогие версии коллекций. Такие методы, как фильтр и отображение на карте, только выполняют переданные функции при обращении к соответствующему элементу. Таким образом, карта на огромной коллекции возвращается сразу, потому что она просто создает обертку вокруг оригинальной коллекции. Только при обращении к элементу отображение фактически выполняется (для этого элемента). Обратите внимание, что View не является классом, но существует множество классов XxxView для различных коллекций.

Ответ 2

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

Однако возникает сложная проблема с "преждевременным исполнением", возникающая при выполнении этого, чего можно избежать с помощью итераторов, но не потоков, и в процессе указывает важную семантическую разницу между ними. Это, возможно, наиболее ярко проиллюстрировано следующим образом:

def runiter(start: Int) {
  // Create a stream that returns successive integers on demand, e.g. 3, 4, 5, ....
  val iter = {
    def loop(v: Int): Stream[Int] = { println("I computed a value", v); v} #:: loop(v+1)
    loop(start)
  }
  // Now, sometime later, we retrieve the values ....
  println("about to loop")
  for (x <- iter) {
    if (x < 10) println("saw value", x) else return
  }
}

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

Результат:

scala> runiter(3)
(I computed a value,3)
about to loop
(saw value,3)
(I computed a value,4)
(saw value,4)
(I computed a value,5)
(saw value,5)
(I computed a value,6)
(saw value,6)
(I computed a value,7)
(saw value,7)
(I computed a value,8)
(saw value,8)
(I computed a value,9)
(saw value,9)
(I computed a value,10)

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

Простая попытка исправить его с использованием начального пустого потока не работает:

def runiter(start: Int) {
  // Create a stream that returns successive integers on demand, e.g. 3, 4, 5, ....
  val iter = {
    def loop(v: Int): Stream[Int] = { println("I computed a value", v); v} #:: loop(v+1)
    Stream[Int]() ++ loop(start)
  }
  // Now, sometime later, we retrieve the values ....
  println("about to loop")
  for (x <- iter) {
    if (x < 10) println("saw value", x) else return
  }
}

Результат (тот же, что и раньше):

scala> runiter(3)
(I computed a value,3)
about to loop
(saw value,3)
(I computed a value,4)
(saw value,4)
(I computed a value,5)
(saw value,5)
(I computed a value,6)
(saw value,6)
(I computed a value,7)
(saw value,7)
(I computed a value,8)
(saw value,8)
(I computed a value,9)
(saw value,9)
(I computed a value,10)

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

def runiter(start: Int) {
  // Create an iterator that returns successive integers on demand, e.g. 3, 4, 5, ....
  val iter = {
    def loop(v: Int): Iterator[Int] = { println("I computed a value", v); Iterator(v)} ++ loop(v+1)
    Iterator[Int]() ++ loop(start)
  }
  // Now, sometime later, we retrieve the values ....
  println("about to loop")
  for (x <- iter) {
    if (x < 10) println("saw value", x) else return
  }
}

Результат:

scala> runiter(3)
about to loop
(I computed a value,3)
(saw value,3)
(I computed a value,4)
(saw value,4)
(I computed a value,5)
(saw value,5)
(I computed a value,6)
(saw value,6)
(I computed a value,7)
(saw value,7)
(I computed a value,8)
(saw value,8)
(I computed a value,9)
(saw value,9)
(I computed a value,10)

Обратите внимание: если вы не добавите начальный пустой итератор, вы столкнетесь с такой же проблемой преждевременного выполнения, как и с потоками.