Ergonomics: the need for a short-and-sweet top-level collection type

Pre-2.13 one could use Seq as a parameter type for a lot of things that are some sort of collection:

def foo(xs: Seq[Int])

and you could pass e.g. List, Array, Buffer and so on to it.

Now in 2.13 the collections library got rewritten and Seq means collection.immutable.Seq and not collection.Seq. So you can’t pass anything mutable to foo in the example.

Solution #1: use collection.Seq instead of Seq

Sure. But it’s not short and sweet. I’d hate to write def foo(xs: scala.collection.Seq[Int]) everywhere.

Solution #2: use Iterable

Yes, technically this is the right solution. It is the top-level collection type that has most of the collection methods, and the name is descriptive and precise.

But it’s not short and sweet. I’d hate to write a lot of def foo(xss: Iterable[Iterable[Int]], yss: Iterable[Iterable[Int]]) and so on.

I prefer short names on anything that is part of the “core language” (e.g. def, val etc.), so that it gets out of the way as much as possible, letting the actual business logic stand out better. And such I’ve used Seq all over the place for collections, even though it might not be “technically” correct. I.e. in the context of most code I write I don’t care if something is “iterable” or whatever that might even mean, I just want to map over it or filter it.

Solution

I suggest adding a short type alias for Iterable that serves as the standard top-level collection type. Coll is a good candiate.

Thus I propose that type Coll[A] = Iterable[A] be added to the standard library.

If you really want to throw away any way to reason about the performance characteristics, thread safety and even semantics of your operations then you can just write your own type alias.

The solution to this is easy, has to be done once per project, and is non-invasive to anyone else:

Create a type alias in leaf module of your project. All other modules depend on it, and all these modules have the alias imported via the -Yimports compiler flag.

3 Likes

I don’t quite see where you’re coming from. Are you saying that you would never write a method like def foo(xs: Iterable[Int])?

Yes, roll-your-own solutions are always possible, but they are inconvenient and IMO decrease maintainability (marginally), so in general I would prefer out-of-the-box solutions, at least for “core” functionality.

Most of the time no. I would only do it if I am consuming all elements in some kind of foreach, at that moment I would ask for an IterableOnce instead, and immediately call iterator on it to consume them.

Anyways I believe here the point here is.
Introducing that change can break a lot of code, so it can’t be done until 3.1 and even ignoring that, I really do not see too much value in such a simple alias, it is just a couple of characters.

Finally, our views of what is good and bad as well as what is core and what not are probably different. But, I really would not want to encourage people to write code using such an abstract type, at that point you do not even know if order and duplicates are preserved or not, not even if its operation is thread safe. Seq is already general enough; I do not like it but I can see its value.

2 Likes

Stepping back for a minute: the change in the semantics of scala.Seq wasn’t done accidentally or casually. It happened because the community decided that having an easy alias that covered both mutable and immutable types was an actively bad idea.

The underlying problem is that serious code should not treat these as being equivalent. In particular, if a collection type is mutable under the hood, that means it is extremely dangerous in a multi-threaded environment (which a large fraction of Scala code is). That doesn’t make it invalid for use, but it does mean you need to handle it with kid gloves, and think very carefully about its usage, because it could be altered in mid-algorithm out from under you.

The implication is that people shouldn’t be casually conflating the mutable and immutable branches, the way they’ve tended to do in the past: they may look alike, but they very much aren’t alike. And idiomatic Scala has settled pretty firmly in favor of Immutable By Default – mutability is a thing, and there are times when it is totally the right thing, but you should be using it deliberately, consciously, and carefully.

So in all seriousness: no. You’re arguing to undo a decision that was made intentionally, after many years of discussion. It’s not going to happen. If you know what you’re doing, and want to handle the two sides as equivalent, do it in your own codebase – as @BalmungSan says, it’s easy enough to add your own alias. But that easy equivalence was intentionally removed from stdlib, and should not be brought back.

6 Likes

Fair. Thanks for your explanation. I didn’t pay attention at the time it was discussed.

1 Like

Entirely reasonable – it wasn’t so much a single discussion, as a long-running undercurrent of, “Having a top-level Seq type that allows mutability is probably a misfeature”.

1 Like