I'm playing with scala generics and type bounds to understand its possible use cases. I'm puzzled with a scenario.
Let's say I have a trait Combinable
trait Combinable[T] {
def combine(other: T): T
}
and I want to implement an implicit def for Vector[A]:
implicit def vectorCombinable[A](self: Vector[A]) = new Combinable[Vector[A]] { // note: using scala 2.11, no SAM support
override def combine(other: Vector[A]): Vector[A] = self ++ other
}
Everything is fine so far, the problem starts if I replace Vector with type B upper bounded to GenTraversable
:
implicit def vectorCombinable[A, B <: GenTraversable[A]](self: B): Combinable[B] = new Combinable[B] {
override def combine(other: B): B = self ++ other
}
I simply want this method to return in type B, but self ++ other
fails with the following compilation error:
Expression of type GenTraversable[A] doesn't conform to expected type B
You can do it like this:
implicit def vectorCombinable[A, B <: GenTraversableLike[A, B]]
(self: B with GenTraversable[A])
(implicit cbf: CanBuildFrom[B, A, B])
: Combinable[B] = new Combinable[B] {
override def combine(other: B): B = self ++ other
}
First, you need B
to extend GenTraversableLike
, because scala.collection.???Like
classes contain both the type of their elements and the full type of the sequence in their signature. For example Vector[Int]
extends GenTraversableLike[Int, Vector[Int]]
. The operations defined on ???Like
classes thus can use the full type of the sequence.
Second, you need self
to be B with GenTraversable[A]
, because the compiler should be able to figure out both the type of the sequence and the type of its elements from a single signature.
Third, you have to provide an implicit CanBuildFrom[B, A, B]
, that proves that you can build a sequence B
with elements of type A
from a sequence B
. This proof will be supplied to the ++
method of GenTraversable
After all that, it works OK:
scala> List(1,2,3).combine(List(4,5,6))
res0: List[Int] = List(1, 2, 3, 4, 5, 6)
scala> Set(1,2,3).combine(Set(4,5,6))
res1: scala.collection.immutable.Set[Int] = Set(5, 1, 6, 2, 3, 4)
scala> Map(1 -> "a", 2 -> "b").combine(Map(1 -> "c", 3 -> "d"))
res2: scala.collection.immutable.Map[Int,String] = Map(1 -> c, 2 -> b, 3 -> d)