docs.scala-lang
docs.scala-lang copied to clipboard
Add examples in https://docs.scala-lang.org/overviews/core/custom-collection-operations.html that don't use ad-hoc polymorphism
I am porting some legacy code from 2.12 to 2.13 (this code originated maybe 10 years ago in the 2.9 or 2.10 days...) and I found this document to help me:
https://docs.scala-lang.org/overviews/core/custom-collection-operations.html
The code in question was a function that took a collection of type CC[A] and transformed it, returning (in this case) CC[Int]. The closest example, in the documentation was this one:
https://docs.scala-lang.org/overviews/core/custom-collection-operations.html#transforming-any-collection
But that example uses ad-hoc polymorphism, and my function does not. Using ad-hoc polymorphism, the solution constructs a class IntersperseOperation that takes a few type parameters, and then defines an intersperse method, which takes even more type parameters, some of which depend on the type parameters of the enclosing class.
I found it rather challenging use this example for my own code. I was hoping we could add some documentation giving an example of how (for example) you might write a method like (this won't compile or be correct, but that's the point - I'd like to see how it could be made to work):
def intersperse[A, CC[A]](xs: CC[A], sep: A): CC[A] = ???
The implementation here might not be that interesting, the key part is understanding how to capture enough information in the type parameters and implicit arguments to be able to use the implementation given already in the document.
Thanks - I want to point out that I was able to follow these docs a few weeks ago to port some code that was using ad-hoc polymorphism - that was pretty painless.
I eventually found one possible solution:
def intersperse[CCA](xs: CCA)(using isSeq: IsSeq[CCA], bf: BuildFrom[CCA, isSeq.A, CCA])(sep: isSeq.A): CCA = ???
Do we have to add it in the documentation?
Thank you for writing this issue @mtomko. The traits IsSeq, IsIterable, etc. are useful to support various types of “collection-like” types such as Array, BitSet, Range, or String. If you want to define an operation that will work on any collection type CC[X] <: Iterable[X] you can do it as follows:
def intersperse[CC[X] <: scala.collection.Iterable[X], A](
xs: CC[A],
sep: A
)(
implicit buildFrom: scala.collection.BuildFrom[CC[A], A, CC[B]]
): CC[A] = {
val builder = buildFrom.newBuilder(xs)
val iterator = xs.iterator
while (iterator.hasNext) {
builder += iterator.next()
if (iterator.hasNext) {
builder += sep
}
}
builder.result()
}
However, keep in mind the limitations of this basic implementation:
- it won’t work with collection types that don’t have the same “shape” as
CC[A](e.g.,Rangewould not work). This can be fixed by using theIsIterabletrick. - the implementation I provided here does not lazily evaluate the collection elements, so it won’t behave as expected when used on a lazy collection type (e.g.,
LazyList). This can be fixed by usingxs.iterableFactory.fromSpecific(…)and then passing aViewimplementation that models the same computation.
I agree that currently the “custom-collection-operations.html” page is not perfect because it shows only the most general solution (with both IsSeq and a lazy implementation), which is the one that behaves as expected in most situations, but is also the most complex one. We could update it to introduce this general solution more progressively. For instance, we could start with a more basic solution (such as the one I gave above), and explain its limitations, and then show how to address them. I am happy to help you if you are interested in improving the documentation.