scalagenericstypeclasstype-boundsf-bounded-polymorphism

Scala Generics Type Bounds - Pointing to the Actual Type


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


Solution

  • 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)