When do you choose to type a given function\'s return type as Seq
vs Iterable
vs Traversable
(or alternatively even deeper within Se
Make your method's return type as specific as possible. Then if the caller wants to keep it as a SuperSpecializedHashMap
or type it as a GenTraversableOnce
, they can. This is why the compiler infers the most specific type by default.
Quick note: With Scala 2.13.x, Traversable
is out of the picture. Iterable
is much more general and it was determined that going forward the duality is not justified. Iterable
is now at the top of the collections hierarchy and Traversable
has been deprecated.
A rule of thumb I follow is, depending on implementation, to make the return types as specific as possible and the types of arguments as general as possible. It's an easy to follow rule and it provides you with consistent guarantees on the type properties with maximum freedom.
Say, if you have a function implementation which just traverses a data structure with methods like map
, filter
or fold
- those that are implemented in the trait Traversable
, you can expect it to perform equally on any type of input collection - be it a List
, Vector
, HashSet
or even a HashMap
, so your input argument should be specified as Traversable[T]
. The choice of output type of the function should only depend on its implementation: in this case it should be Traversable
too. If however in your function you force this data structure to some more specific type with methods like toList
, toSeq
or toSet
, you should specify the appropriate type. Notice the consistency between the implementation and the return type?
If your function accesses the elements of input by index, the input should be specified as IndexedSeq
, as it is the most general type that provides you with guarantees on effective implementation of method apply
.
In case of abstract members the same rule applies with the only difference that you should specify the return types based on how you plan to use them instead of implementation, thus most often they will be more general than in implementation. The categorical choices Seq
, Set
or Map
are the most expected.
Following this rule you protect yourself from very common cases of bottleneck when, for instance, items get appended to List
or contains
gets called on a Seq
instead of a Set
, yet your program remains a nice degree of freedom and is consistent in sense of choice of types.
This is a good question. You have to balance two concerns:
Where (1) asks you to be as little specific about the type (e.g. Iterable
over Seq
), and (2) asks you the opposite.
Even if the return type is just Iterable
, you can still return let's say a Vector
, so if the caller wishes to gain extra power, it can just call .toSeq
or .toIndexedSeq
on it, and that operation is cheap for a Vector
.
As a measure of the balance, I would add a third point:
Seq
. If you can assume that no two equal objects can occur, give a Set
. Etc.Here are my rules of thumb:
Set
, Map
, Seq
, IndexedSeq
List
in favour of Seq
. It allows the caller to do pattern matching with the cons extractorscollection.immutable.Set
, collection.immutable.IndexedSeq
)Vector
), but the general type (IndexedSeq
) which gives the same APIIterator
instances, the caller can then easily generate a strict structure, e.g. by calling toList
on itIndexedSeq
Of course, this is my personal choice, but I hope it sounds sane.
Seq
by default everywhere.IndexedSeq
when you need to access by index.These are the "common-sense" guidelines. They are simple, practical, and work well in practice while balancing principles and performance. The principles are:
Seq
satisfies both principles. As described in http://docs.scala-lang.org/overviews/collections/seqs.html:
A sequence is a kind of iterable that has a [finite] length and whose elements have fixed index positions, starting from 0.
90% of the time, your data is a Seq.
Other notes:
List
is an implementation type, so you shouldn't use it in an API. A Vector
for instance can't be used as a List
without going through a conversion.Iterable
doesn't define length
. Iterable
abstracts across finite sequences and potentially infinite streams. Most of the time one is dealing with finite sequences so you "have a length," and Seq
reflects that. Frequently you won't actually make use of length. But it's needed often enough, and is easy to provide, so use Seq
.Drawbacks:
There are some slight downsides to these "common-sense" conventions.
case head :: tail => ...
. You can use :+
and +:
as described here. Importantly, however, matching on Nil
still works as described in Scala: Pattern matching Seq[Nothing].Footnotes:
Map
here because the question, sensibly, doesn't ask about it.