scalaextension-methodsimplicitscala-collections

How to properly use collection factories in scala 2.13?


Something like this:

implicit class PairOps[A, B, C[X] <: Iterable[X]](val c: C[(A,B)]) extends AnyVal {
   def swap = c.map { case (a, b) => (b, a) } 
}

Kinda works ... except that val foo: Seq[(Int, String)] = Seq(("foo",1)).swap does not compile, because swap returns Iterable rather than a Seq.

How do I fix it? There used to be breakOut in 2.12 that was using some magic (that I never quite understood tbh) to do this kind of thing ... but now I need a Factory ...

Tried adding it as an implicit param:

   def swap(implicit f: Factory[(B,A),C]) = c.map { case (a,b) => (b,a) }.to(f) }

This compiles with the right type, but I can't use it, because I don't have that implicit in scope at the call site (even swap(Seq) does't work for some reason, even though swap.to(Seq) does (in the first version, without implicit factory) ...

Can someone please set me straight here? There must be a way to accomplish what I want here, but I can't figure out the right incantation :(


Solution

  • What works for me is modifying the implicit you are fetching:

    import scala.collection.Factory
    
    implicit class PairOps[A, B, C[X] <: Iterable[X]](val c: C[(A,B)]) extends AnyVal {
       def swap(implicit f: Factory[(B, A), C[(B, A)]]) = c.map { case (a, b) => (b, a) }.to(f)
    }
    
    List(1 -> "test").swap
    

    see Scastie

    The reason for the verbosity is that ask for [A, C[_]] and make C[A] out of it approach which was used prior to 2.13 doesn't work if you want to do e.g.

    Factory[(Int, String), Map[Int, String]]
    

    (You can check that in 2.12 when you use .to(Map) (e.g with scala-collection-compat) you'll end up with a Map... upcasted to Iterable[(K, V)] precisely because .to interface operates on [A, Coll[_]] and to be able to shove Map factory there, it has to upcast it to Iterable factory.)

    In other words the verbosity added by by 2.13 allows Factory to be used in cases like seq.map(a => a -> a.toString).to(Map) which eliminated the need for CanBuildFrom and separate syntax for .to[Coll] and .toMap.