Default Sequence type

To what sequence type should I default in every-day development? What type should be annotated as type ascription?

Until now I would use something like val sequence = Seq(1, 2, 3) if I need a sequence.

But today I read that we should default to IndexSeq(1, 2, 3) which returns an Vector[Int].

I am now confused and wondering: What’s the right way to go?

1 Like

This is a very opinion based question. So, here is my opinionated answer.

In general I do not like Seq, both as a type and as a builder of collections, because it tells you very little about the runtime behaviour of the concrete collection; this provides an excellent discussion about that problem.

Thus, I prefer to use concrete types for most of my code. And here is my list of used types.

  • List as my general collection type, I use it 90% of the time. It is easy to reason about how you should use it. Also, perfect for tail-recursive functions.
  • ArraySeq for when I need fast index access or I am building something that will be passed to a Java method which expects an Array.
  • Set when I require uniqueness and fast existence check.
  • Map when I need a to get value given some key.
  • LazyList when I need a simple lazy and maybe infinite collection. (it’s unfold method is very useful).
  • mutable.ListBuilder when, for performance reasons, I need to build a List imperatively.

(If you are in 2.12 change LazyList with Stream and ArraySeq with Vector)

Outside of the stdlib.

  • cats.data.NonEmptyChain when I need a non-empty collection with fast append. (usually used to collect errors).
  • fs2.Stream when I need a more robust and async streaming collection.

And when I want to write a generic method that accepts any collection in the stdlib. I would use TraversableOnce to get an Iterator.
BTW, I also use Iterators internally in some function I chain a lot of transformations, to make all them lazy.

And that is it. Hope it helps.
As I said, this is just my opinion and preferences.
I recommend you to wait to see what do other people say about this. Take that, together with your own experience to form your own opinion.

2 Likes

Thank you so much for your answer! I know it is an opinion based questions and I am gladly thankful that you took the time to share your opinion.

Before I dug deeper into Scala’s collection framework I would use List as my default collection type. After reading more about Scala I got the impression that it is recommended to use Seq or IndexedSeq because their factory methods will always use the collection type which is recommended for general purposes, which could change over the time (like ArraySeq to Vector.)

Lately I read that we should use Vector as a general purpose - and than I was not anymore sure where to go.

I hope we will able to collect some more opinions in this thread.

Also opinions –

List and Vector are both decent choices, but have wildly different performance characteristics. So it sort of depends on what you need to do. For problems where List works well, I will usually default to that – as @BalmungSan says, I probably use that 90% of the time.

I sometimes use Vector for random access, and sometimes for a collection where I need to add values other than at the front, but it’s a bit controversial. Vector is good at everything, but it isn’t great at anything, and its performance characteristics are a little hard to describe concisely. (Many operations are often referred to as “effectively constant time”, which makes some folks quite unhappy.) So it’s great for beginners, but folks doing serious work, especially if it requires high (and well-defined) performance, tend to look elsewhere.

As for Seq – when I started in Scala, it was pretty common to use Seq everywhere, and I still do so sometimes. But it loses a lot of terribly important performance information: it’s vague and mushy. So I think most of the community has moved away from using it routinely at this point, in favor of more concrete types. (Or at least, more precise traits.) I will sometimes use it as a function parameter for easy problems, but there is rarely a principled reason to use it – most problems are either flexible enough that you could use Iterable instead, or they call for more-precise behavior, so you want one of the sub-traits.

(And the base scala.collection.Seq is arguably slightly evil, since it allows mutable values to be passed as parameters, which is almost never what you want.)

3 Likes

Thank you for your thoughts!

This Part I do not quite understand: How can any collection prevent you from adding mutable objects?

He means scala.collection.Seq itself may be mutable. If you want an immutable Seq, use scala.collection.immutable.Seq.

1 Like

That’s not quite what I mean. Consider: types like List and Vector are immutable. You can create a new one based on an existing one, but you can never change a List or Vector. That’s very powerful, and very important.

But there are mutable types in Scala’s collections as well – things like Array or Buffer. Since those are mutable, it means that some other thread could change them while you are in the middle of your function using them. That makes code ferociously unpredictable and non-deterministic.

So it’s usually better not to mix these up. If you ever need to use a mutable data structure, you should say so explicitly, and be clear in the functions that are calling it that this could happen. In general, I recommend avoiding using parameter types where the passed-in values could be either mutable or immutable, because it makes it hard to write reliable code.

So in general, if you are going to use Seq, make sure it is scala.collection.immutable.Seq.

(This is a longtime gotcha, because plain old scala.Seq used to allow either. This changed recently…)

2 Likes

As far as I understand Seq is by default immutable due to the type alias in Predef.

That’s true, but it’s a very recent change…

1 Like