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

Scala, рекомендации по типу возврата - при выборе seq, iterable, traversable

Когда вы выбираете тип возвращаемого типа заданной функции как Seq vs Iterable vs Traversable (или, альтернативно, еще глубже в иерархии Seq)?

Как вы принимаете это решение? У нас есть много кода, который возвращает Seq по умолчанию (обычно начиная с результатов запроса БД и последовательных преобразований). Обычно я хочу, чтобы по умолчанию возвращались типы Traversable и Seq, когда специально ожидал данный порядок. Но у меня нет сильного оправдания для этого.

Я отлично знаком с определением каждого признака, поэтому, пожалуйста, не отвечайте с определением терминов.

4b9b3361

Ответ 1

Это хороший вопрос. Вы должны уравновесить две проблемы:

  • (1) попытайтесь сохранить свой общий API, чтобы позже вы могли изменить реализацию
  • (2) предоставить вызывающему абоненту некоторые полезные операции для выполнения в коллекции

Где (1) запрашивает у вас как малое значение типа (например, Iterable over Seq), и (2) запрашивает обратное.

Даже если тип возвращаемого значения равен Iterable, вы все равно можете вернуть let say a Vector, поэтому, если вызывающий абонент хочет получить дополнительную мощность, он может просто называть .toSeq или .toIndexedSeq на нем, и эта операция дешева для Vector.

В качестве меры баланса я добавлю третий пункт:

  • (3) используйте тип, отражающий способ организации данных. Например. когда вы можете предположить, что данные имеют последовательность, дайте Seq. Если вы можете предположить, что не существует двух одинаковых объектов, дайте Set. Etc.

Вот мои эмпирические правила:

  • попробуйте использовать только небольшой набор коллекций: Set, Map, Seq, IndexedSeq
  • Я часто нарушаю это предыдущее правило, используя List в пользу Seq. Это позволяет вызывающему пользователю выполнить сопоставление образцов с экстрактами cons
  • использовать только неизменяемые типы (например, collection.immutable.Set, collection.immutable.IndexedSeq)
  • не используют конкретные реализации (Vector), но общий тип (IndexedSeq), который дает тот же API
  • если вы инкапсулируете измененную структуру, возвращаете только экземпляры Iterator, вызывающий может затем легко создать строгую структуру, например. вызывая toList на нем
  • Если ваш API небольшой и четко настроен на "большую пропускную способность данных", используйте IndexedSeq

Конечно, это мой личный выбор, но я надеюсь, что это звучит разумно.

Ответ 2

Сделайте тип возвращаемого метода как можно более конкретным. Затем, если вызывающий абонент хочет сохранить его как SuperSpecializedHashMap или введите его как GenTraversableOnce, они могут. Вот почему компилятор по умолчанию указывает наиболее специфический тип.

Ответ 3

  • Использовать Seq по умолчанию везде.
  • Используйте IndexedSeq, когда вам нужно получить доступ по индексу.
  • Используйте что-либо еще только в особых обстоятельствах.

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

  • Используйте тип, который отражает способ организации данных (спасибо OP и ziggystar).
  • Использовать типы интерфейса как в аргументах метода, так и в типах возвращаемых значений. Оба входа и возвращаемые типы API выигрывают от гибкости общности.

Seq удовлетворяет обоим принципам. Как описано в http://docs.scala-lang.org/overviews/collections/seqs.html:

Последовательность является разновидностью итерации, которая имеет [конечную] длину и элементы которой имеют фиксированные позиции индекса, начиная с 0.

90% времени, ваши данные являются Seq.

Другие примечания:

  • List - это тип реализации, поэтому вы не должны использовать его в API. A Vector, например, не может использоваться как List без прохождения преобразования.
  • Iterable не определяет length. Iterable рефераты через конечные последовательности и потенциально бесконечные потоки. В большинстве случаев речь идет о конечных последовательностях, поэтому вы "имеете длину", а Seq отражает это. Часто вы фактически не используете длину. Но это достаточно часто, и его легко обеспечить, поэтому используйте Seq.

Недостатки:

Есть некоторые незначительные минусы этих соглашений "здравого смысла".

  • Вы не можете использовать сопоставление шаблонов списков, т.е. case head :: tail => .... Вы можете использовать :+ и +:, как описано здесь. Однако важно отметить, что сопоставление на Nil по-прежнему работает, как описано в Scala: соответствие шаблону Seq [Nothing].

Сноска:

Ответ 4

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

Скажем, если у вас есть реализация функции, которая просто пересекает структуру данных с помощью таких методов, как map, filter или fold - те, которые реализованы в признаке Traversable, вы можете ожидать, что она будет работать одинаково для любого типа ввода - будь то List, Vector, HashSet или даже HashMap, поэтому ваш входной аргумент должен быть указан как Traversable[T]. Выбор типа вывода функции должен зависеть только от ее реализации: в этом случае он должен быть Traversable тоже. Если, однако, в вашей функции вы привязываете эту структуру данных к более определенному типу с помощью методов типа toList, toSeq или toSet, вы должны указать соответствующий тип. Обратите внимание на согласованность между реализацией и возвращаемым типом?

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

В случае абстрактных членов применяется одно и то же правило с той лишь разницей, что вы должны указывать типы возврата, основанные на том, как вы планируете использовать их вместо реализации, поэтому чаще всего они будут более общими, чем в реализации. Наиболее ожидаемые категориальные варианты Seq, Set или map.

Следуя этому правилу, вы защищаете себя от очень распространенных случаев узкого места, когда, например, элементы, добавляемые к List или contains, вызывается в Seq вместо Set, но ваша программа остается хорошая степень свободы и последовательна в смысле выбора типов.