Scala.collection.breakOut vs views

Этот ответ SO описывает, как scala.collection.breakOut можно использовать для предотвращения создания расточительных промежуточных коллекций. Например, здесь мы создаем промежуточный Seq[(String,String)]:

val m = List("A", "B", "C").map(x => x -> x).toMap

Используя breakOut, мы можем предотвратить создание этого промежуточного Seq:

val m: Map[String,String] = List("A", "B", "C").map(x => x -> x)(breakOut)

Views решает ту же проблему и, кроме того, элементы доступа лениво:

val m = (List("A", "B", "C").view map (x => x -> x)).toMap

Я предполагаю, что создание оберток View довольно дешево, поэтому мой вопрос: есть ли настоящая причина использовать breakOut над View s?

Ответ 1

Я не думаю, что views и breakOut идентичны.

A breakOut - это реализация CanBuildFrom, используемая для упрощения операций преобразования путем исключения промежуточных шагов. Например, получить от А до Б без посреднической коллекции. A breakOut означает, что Scala выберите соответствующий объект-строитель для максимальной эффективности создания новых элементов в данном сценарии. Подробнее здесь.

views имеют дело с другим типом эффективности, основным этапом продажи является: "Больше нет новых объектов". В представлениях хранятся световые ссылки на объекты для решения различных сценариев использования: ленивый доступ и т.д.

Нижняя строка:

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

collection.view.map(somefn)(breakOut)

Чем от:

collection.view.map(someFn)

Ответ 2

Вы собираетесь совершить поездку из Англии во Францию.

С просмотром: вы берете набор заметок в своем ноутбуке и бума, как только вы вызвали .force(), вы начинаете создавать все из них: buy a ticket, board on the plane, ....

С breakOut: вы уходите и бумете, вы в Париже, глядя на Эйфелеву башню. Вы не помните, как именно вы прибыли туда, но вы действительно совершили эту поездку, просто не сделали никаких воспоминаний.

Плохая аналогия, но я надеюсь, что это даст вам представление о том, в чем разница между ними.

Ответ 3

Что сказал флавиан.

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

val v = original.view
val suffixes = v.tails

представления исходной строки. Затем вы можете циклически перебирать суффиксы один за другим, используя suffix.force(), чтобы преобразовать их обратно в строки в цикле, таким образом, только удерживая один в памяти за раз. Конечно, вы могли бы сделать то же самое, перебирая свой собственный цикл по индексам исходной строки, а не создавая какую-либо коллекцию суффиксов.

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

Если у вас действительно есть случай, когда выбор между ними имеет смысл, предпочитайте breakOut, если нет хорошего аргумента для использования представления (например, выше).

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