What operation methods go to IterableOnceOps, IterableOps and Iterator?

Hi everyone, first time here.

I have a question about API design of IterableOnceOps , IterableOps and Iterator. The same question was posted on scala gitter, not didn’t got any response. So I thought repost here.

Looking at various methods declared/defined in IterableOnceOps , IterableOps and Iterator , it comes to me the following question:

Why are some operation methods declared/defined in IterbleOnceOps , then get implemented/overriddn in IterableOps and Iterator , while some others are declared/defined separately both in IterableOps and Iterator without a general declaration/definition in IterableOnceOps ?

I know some methods don’t make sense for one trait or another, therefore not suitable to be placed in IterableOnceOps .

But as I checked, zip , zipWithIndex , zipAll , scanLeft , partition , concat are all implemented for IterableOps and Iterator , and all these methods can be genericly declared in IterableOnceOps . But only zipWithIndex is actually declared in IterableOnceOps .

Taking zip as an examle.

IterableOps.zip has the following signature:

def zip[B](that: IterableOnce[B]): CC[(A @uncheckedVariance, B)]

Iterator.zip has the following definition snippet:

def zip[B](that: IterableOnce[B]): Iterator[(A, B)]

They could be genericly declared as IterableOnceOps.zip :

def zip[B](that: IterableOnce[B]): CC[(A @uncheckedVariance, B)]

This is exactly the case done for zipWithIndex .

So, why is only zipWithIndex declared in IterableOnceOps , but for zip , zipAll , scanLeft , partition , concat , they are not declared in IterableOnceOps ?

1 Like

A reason to define a method in a trait and then again in a trait which is a subtype, as is done with zip above, is that we can refine the type. Zipping an Iterable object with an IterableOnce gives an Iterable, while this is not true for arbitrary IterableOnce objects.

As the scaladocs say, the operations defined in IterableOnceOps are those common to Iterator (which can be read only once) and Iterable. The operation zipWithIndex is common, but not zip, since we can zip an Iterator with another Iterator and an Iterable with another Iterable.


Sorry, I didn’t get your point as what prevents us from generalizing zip into IterableOnceOps.

I failed to see why zip is not common for Iterable and Iterator.

When Iterable trait is defined, it has the following signature:

traitIterable[+A] extends IterableOnce[A] with IterableOps[A, Iterable, Iterable[A]] with IterableFactoryDefaults[A, Iterable]

which defines CC == Iterable

When Iterator trait is defined, it has the following signature:

Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Iterator[A]]

which defines CC == Iterator.

Now if we just declare zip in IterableOnceOps as

def zip[B](that: IterableOnce[B]): CC[(A @uncheckedVariance, B)]

every type signature just matches. For Iterable, CC will be Iterable; for Iterator, CC will be Iterator. Everything works.

And as I said, this is exactly how zipWithIndex is implemented in the source code. Firstly declare it in IterableOnceOps, then special case it in both IterableOps and Iterator.

Maybe I have misunderstood something, if that is the case, please enlighten me.

Thank you.

It’s probably just an oversight. These methods may have been added independently on both sides. This would have been worth correcting in 2.14 (which we expected at the time we worked on this for 2.13) but now it has to wait much longer.

I see.
Now it has to wait for Scala 3.

I personally think this point hasn’t been brought up enough. 2.14 was planned to be a bit of a clean up release before Scala 3. But now IIUC any backwards incompatible cleanups will actually have to wait until Scala 4.

I’m pretty sure that’s not true. Consider that 2.13 was an enormous round of library modifications, and there was nothing special about that release number. Scala 3 can and no doubt will include further stdlib changes in the various 3.x releases. They just Really Really Don’t Want To Do It Now, to minimize the number of moving parts during the upcoming 2 -> 3 transition.

In other words, there’s nothing magical about making changes in 2.x instead of 3.x – it’s just a question of logistics and timing. The only things that have happen at a specific time are the big breaking changes, which need to happen in 3.0. But that’s generally language changes, not library ones.

I thought that from Scala 3 onwards we would have semantic versioning. Meaning that forward incompatible changes are possible in Scala 3.2 or somesuch but nothing backwards incompatible. I might remember incorrectly.

Hmm – possible, I suppose, but I don’t recall hearing anything about that. Certainly there are serious changes coming in terms of what the word “version” means, because of TASTy, but I hadn’t heard anything saying that backwards-incompatible changes would be impossible going forward…