Arturs Vancans
Arturs Vancans

Reputation: 4640

Implicit conversion of Traversable contents in Scala

I am trying to create an implicit converter that would would use a implicit converter that's currently in scope (eg. A => B) and would be able to convert any type of Traversable[A] to Traversable[B].

So far I got:

implicit def convertLists[A, B](from: Traversable[A])(implicit conv: A => B): Traversable[B] = from.map(conv)

This however does not work with:

val listOfB: List[B] = convertLists(List[A]())

If I change Traversable to List, then it works fine, eg:

implicit def convertLists[A, B](from: List[A])(implicit conv: A => B): List[B] = from.map(conv)

Do I need to add anything else to allow the converter to accept any subclass of Traversable?

Upvotes: 5

Views: 973

Answers (2)

Łukasz
Łukasz

Reputation: 8673

Using the approach in scala collections library you could come out with such code:

import scala.collection.generic.CanBuildFrom
import scala.collection.TraversableLike

implicit val boolToInt = (b: Boolean) => if (b) 1 else 0
implicit def convertLists[A, B, Repr, That](from: TraversableLike[A, Repr])(implicit conv: A => B, bf: CanBuildFrom[Repr, B, That]): That = from map conv
val listOfB: List[Int] = List(true, false)

which gives

listOfB: List[Int] = List(1, 0)

Upvotes: 2

Kolmar
Kolmar

Reputation: 14224

You have explicitly defined convertLists to return Traversable[B]. Traversable is not a subtype of List (it's its supertype), so the result of convertLists (Traversable) can't be a return type of listOfB (List).

You can define convertLists to infer the result type based on the type of its argument, if you use CanBuildFrom:

import scala.collection.TraversableLike
import scala.collection.generic.CanBuildFrom
import scala.language.higherKinds

// `CC` is some concrete subtype of `Traversable`
// `That` is an automatically inferred result collection type
implicit def convertLists[A, B, CC[T] <: TraversableLike[T, CC[T]], That](
  from: CC[A]
)(
  implicit
    conv: A => B,
    // witness that it's possible to build 
    // a collection with elements `B` from a collection `CC[A]`, 
    // and compute the resulting collection type `That`
    bf: CanBuildFrom[CC[A], B, That]
): That = from.map(conv)

Now assuming the following simple definition of A and B

case class A(i: Int)
case class B(i: Int)
implicit def aisb(a: A): B = B(a.i)

The following works:

val listOfB: List[B] = convertLists(List[A](A(1)))

And you don't have to call convertLists explicitly:

val listOfB2: List[B] = List[A](A(1))

Upvotes: 2

Related Questions