Yann Moisan
Yann Moisan

Reputation: 8281

How to transform a collection of tuples with scala 2.13

I'd like to migrate the following code from Scala 2.12 to 2.13

Given any collection of tuples Coll[(A, B)] and a method f: B => IterableOnce[C], I'd like to produce a Coll[(A, C)] by applying f on the second element of the tuple.

implicit class TuplesOps[A, B, Repr <: Traversable[(A, B)]](val s: TraversableLike[(A, B), Repr]) extends AnyVal {
  def flatMapValues[C, That](f: B => TraversableOnce[C])(implicit bf: CanBuildFrom[Repr, (A, C), That]) =
    s.flatMap { case (a, b) => f(b).map((a, _))}
}

I know that the collection API has changed in 2.13 and I read : https://docs.scala-lang.org/overviews/core/custom-collection-operations.html

I've tried 2 implementations

First

import scala.collection.{ AbstractView, BuildFrom }
import scala.collection.generic.IsSeq

object Evidence extends App {
  class TuplesOps[Repr, S <: IsSeq[Repr]](coll: Repr, seq: S) {
    def flatMapValues[B, C, D, That](f: C => IterableOnce[D])(implicit bf: BuildFrom[Repr, (B, D), That], ev: seq.A =:= (B, C)): That = {
      val seqOps = seq(coll)
      bf.fromSpecific(coll)(new AbstractView[(B, D)] {
        override def iterator: Iterator[(B, D)] = {
          seqOps.flatMap { x => f(ev(x)._2).map((ev(x)._1, _))}.iterator
        }
      })
    }
  }

  implicit def TuplesOps[Repr](coll: Repr)(implicit seq: IsSeq[Repr]): TuplesOps[Repr, seq.type] =
    new TuplesOps(coll, seq)

  List("a"->1, "b"->2).flatMapValues{(x:Int) => Seq.fill(x)("x")}
}

And I have this compilation error :

[error] /home/yamo/projects/perso/coll213/src/main/scala/example/Evidence.scala:22:37: diverging implicit expansion for type scala.collection.BuildFrom[List[(String, Int)],(B, String),That]
[error] starting with method Tuple9 in object Ordering
[error]   List("a"->1, "b"->2).flatMapValues{(x:Int) => Seq.fill(x)("x")}

Second

import scala.collection.{ AbstractView, BuildFrom }
import scala.collection.generic.IsSeq

object Aux extends App {
  type Aux[Repr, B, C] = IsSeq[Repr] { type A = (B, C)}

  class TuplesOps[Repr, B, C, S <: Aux[Repr, B, C]](coll: Repr, seq: S) {
    def flatMapValues[D, That](f: C => IterableOnce[D])(implicit bf: BuildFrom[Repr, (B, D), That]): That = {
      val seqOps = seq(coll)
      bf.fromSpecific(coll)(new AbstractView[(B, D)] {
        // same as before
        override def iterator: Iterator[(B, D)] = {
          seqOps.flatMap { case (b, c) => f(c).map((b, _))}.iterator
        }
      })
    }
  }

  implicit def TuplesOps[Repr, B, C](coll: Repr)(implicit seq: Aux[Repr, B, C]): TuplesOps[Repr, B, C, seq.type] =
    new TuplesOps(coll, seq)

  List("a"->1, "b"->2).flatMapValues(Seq.fill(_)("x"))
}

And I have this compilation error

[error] /home/yamo/projects/perso/coll213/src/main/scala/example/Aux.scala:24:24: value flatMapValues is not a member of List[(String, Int)]
[error]   List("a"->1, "b"->2).flatMapValues(Seq.fill(_)("x"))

Could you help me to make both solution work, if possible ?

Upvotes: 3

Views: 417

Answers (1)

jwvh
jwvh

Reputation: 51271

I agree, the Custom Collection instructions aren't what they should be.

I find the Factory method a bit easier than the BuildFrom, but I'm still getting used to either/both of them.

import scala.collection.Factory

implicit
class TuplesOps[A,B,CC[x] <: Iterable[x]](val ts: CC[(A,B)]) {
  def flatMapValues[C](f: B => IterableOnce[C]
                      )(implicit fac: Factory[(A,C), CC[(A,C)]]
                       ): CC[(A,C)] =
    ts.flatMap{case (a,b) => f(b).iterator.map(x => (a,x))}
      .to[CC[(A,C)]](fac)
}

This passes all my simple (minded) tests. It won't work on an Array or an Iterator but then your original Scala 2.12.x version didn't either (for me) so I figured that was okay.

Upvotes: 3

Related Questions