Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I easily convert from one collection type to another during a filter, map, flatMap in Scala?

Suppose I have a List[Int], and I want to call toString on each element, and get back the result as a Vector[String].

What are the various ways to do this in Scala? Is there a solution with a minimal amount of explicit typing? — i.e., I want to specify that I want a Vector rather than a List, but I'd like the String argument to be inferred from the filter function.

Or should I explicitly pass a CanBuildFrom instance? Where do I get these from — for Seqs, Sets and Maps?

like image 476
Jean-Philippe Pellet Avatar asked Apr 08 '11 09:04

Jean-Philippe Pellet


1 Answers

Using breakOut

Use breakOut as the CanBuildFrom and let the typer know what you want your result type to be (unfortunately you need to specify String here)

scala> import collection.breakOut
import collection.breakOut

scala> List(1, 2, 3)
res0: List[Int] = List(1, 2, 3)

scala> res0.map(_.toString)(breakOut) : Vector[String]
res2: Vector[String] = Vector(1, 2, 3)

Using to[Collection] (starting with Scala 2.10.0)

Scala 2.10.0 introduced an easy way to convert a collection to another collection:

scala> List(1, 2, 3).map(_.toString).to[Vector]
res0: Vector[String] = Vector(1, 2, 3)

Using toIndexedSeq

Alternatively ask for an IndexedSeq explicitly:

scala> res0.map(_.toString).toIndexedSeq
res4: scala.collection.immutable.IndexedSeq[String] = Vector(1, 2, 3)

If you want to do this without creating the intermediate List, then:

scala> res0.view.map(_.toString).toIndexedSeq
res5: scala.collection.immutable.IndexedSeq[String] = Vector(1, 2, 3)

Using Natural Transformations

You could do it (awkwardly, but more generally) using natural transformations

scala> trait Trans[F[_], G[_]] {
 | def f2g[A](f : F[A]) : G[A]
 | }
defined trait Trans

Now provide a typeclass instance from the List ~> Vector transformation:

scala> implicit val List2Vector = new Trans[List, collection.immutable.Vector] {
 | def f2g[A](l : List[A]) : Vector[A] = l.map(identity[A])(collection.breakOut)
 | }
List2Vector: java.lang.Object with Trans[List,scala.collection.immutable.Vector] = $anon$1@56329755

Define a wrapper and an implicit conversion:

scala> class Clever[M[_], A](ma : M[A]) { def to[N[_]](implicit t : Trans[M, N]) : N[A] = t.f2g(ma) }
defined class Clever

scala> implicit def ma2clever[M[_], A](ma : M[A]) = new Clever[M, A](ma)
ma2clever: [M[_],A](ma: M[A])Clever[M,A]

Then:

scala> List(1, 2, 3).map(_.toString).to[Vector]
res4: Vector[java.lang.String] = Vector(1, 2, 3)
like image 152
oxbow_lakes Avatar answered Nov 02 '22 14:11

oxbow_lakes