How to properly use collection factories in scala 2.13?

167 Views Asked by At

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 :(

2

There are 2 best solutions below

1
Mateusz Kubuszok On BEST ANSWER

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.

3
stefanobaghino On

Took me a bit of fiddling around and to be very honest I'm not 100% sure of why this works (and if anyone comes along with an explanation you should probably accepts their answer), but it looks like this gets the job done:

implicit class PairOps[A, B, C[X]](val c: C[(A, B)]) extends AnyVal {
  def swap(implicit ev: C[(A, B)] <:< collection.IterableOnceOps[(A, B), C, _]): C[(B, A)] =
    c.map(_.swap)
}

The returned type is the one of the collection wrapped by PairOps. You can play around with this code here on Scastie.