1

I have some old code which relied on implicit CanBuildFrom in order to build a collection of a type specified as a type parameter. Since 2.12, in new collection library, replacement BuildFrom doesn't offer a no-arg Builder factory method. What I want is the IterableFactory for the collection, but these are companion objects, which are not implicit. Can I somehow port it without introducing my own implicit factory class wrapping a Factory for every collection class in the library? I know that there are many flavours to those factories, but even if I had to add a special case for those accepting implicit evidence, it would still be much better than what I have.

A sensible alternative in new code would probably be to take the IterableFactory as a (value) argument instead of relying on explicit type arguments, but it would require changes in too many places, so I'd rather stick to the current architecture and do the boilerplate.

Turin
  • 2,208
  • 15
  • 23
  • 1
    Does this help? https://stackoverflow.com/questions/56551015/how-to-make-method-return-the-same-generic-as-the-input – Kolmar Dec 23 '20 at 05:30

2 Answers2

1

If you want to build a generic collection element-by-element with a Builder and without a prior existing collection, you can use an implicit Factory argument. For example:

import scala.collection.Factory

class Filler[T](makeElement: Int => T) {
  def apply[C[_]](n: Int)(implicit factory: Factory[T, C[T]]): C[T] = {
    val builder = factory.newBuilder
    for (i <- 1 to n) builder += makeElement(i)
    builder.result()
  }
}

And you can use it like this:

scala> val fill = new Filler(_.toString)
fill: Filler[String] = Filler@154f8280

scala> fill[Vector](10)
res0: Vector[String] = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

scala> fill[Set](10)
res1: Set[String] = HashSet(8, 4, 9, 5, 10, 2, 7, 3, 6, 1)

scala> fill[Array](10).toSeq
res2: Seq[String] = ArraySeq(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

There are individual implicit Factorys for Strings and Arrays provided in the standard library. And for all the Iterables (List, Map, Set, etc.) it works, because the companion objects of Iterables extend the IterableFactory class, that provides an implicit def iterableFactory[A]: Factory[A, CC[A]] method.

Kolmar
  • 14,086
  • 1
  • 22
  • 25
  • I feel like a moron. I don't know what I did wrong, because I was sure I did try that. Maybe asked for an implicit `IterableFactory`? Oh well - thank you! – Turin Dec 30 '20 at 19:32
0

You can use type arguments instead of implicits.

def listToString[A, CC[x] <: Iterable[x]](
    list: collection.IterableOps[A, CC, CC[A]]
): CC[String] =
  list.map(x => x.toString)

Using it:

listToString(List(1, 2, 3, 4))
-> List(1, 2, 3, 4): List[String]

listToString(Set("foo", "bar", "baz"))
-> Set(foo, bar, baz): scala.collection.immutable.Set[String]
user
  • 7,435
  • 3
  • 14
  • 44