Scala, рекомендации по типу возврата - при выборе seq, iterable, traversable
Когда вы выбираете тип возвращаемого типа заданной функции как Seq
vs Iterable
vs Traversable
(или, альтернативно, еще глубже в иерархии Seq
)?
Как вы принимаете это решение? У нас есть много кода, который возвращает Seq
по умолчанию (обычно начиная с результатов запроса БД и последовательных преобразований). Обычно я хочу, чтобы по умолчанию возвращались типы Traversable
и Seq
, когда специально ожидал данный порядок. Но у меня нет сильного оправдания для этого.
Я отлично знаком с определением каждого признака, поэтому, пожалуйста, не отвечайте с определением терминов.
Ответы
Ответ 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
, но ваша программа остается хорошая степень свободы и последовательна в смысле выбора типов.