tabiul tabiul - 3 months ago 10
Scala Question

Using Parametric Type in Implicit for Type Classes in Scala

I am trying to learn more about TypeClasses in Scala and came up with this example

trait Cons[T] {
def cons(t1: T, t2: T):T
}

object Cons {
implicit object StringCons extends Cons[String] {
override def cons(t1: String, t2: String): String = t1 + t2
}

implicit object ListCons extends Cons[List[_]] {
override def cons(t1: List[_], t2: List[_]): List[_] = t1 ++ t2
}

implicit object IntCons extends Cons[Int] {
override def cons(t1: Int, t2: Int): Int = Integer.parseInt(t1.toString + t2.toString)
}
}

def Cons[T](t1: T, t2: T)(implicit c: Cons[T]):T = {
c.cons(t1, t2)
}

Cons("abc", "def") // abcdef
Cons(1, 2) // 12
Cons(List(1,2,3),List(4,5,6)) // does not work, as the expected type is List[Int]


When I created
ListCons
I explicitly have set the type of
List[_]
which is if I understand correctly is existential types and is equivalent to Java wildcard which means it is of some type and we do not care.

Now the question is why this did not work. Is there a way to make it work. Or maybe there is some fundamental misunderstanding by me.

Answer

The reason Cons(List(1,2,3),List(4,5,6)) doesn't work is because by type inference rules it means Cons[List[Int]](List[Int](1,2,3), List[Int](4,5,6)), so it specifically needs an implicit Cons[List[Int]], and a Cons[List[_]] is not a Cons[List[Int]].

Cons[List[_]] would be a Cons[List[Int]] if Cons was contravariant (declared as Cons[-T]), but it can't be contravariant because it has a method returning T (unless you cheat with @uncheckedVariance, and you shouldn't).

The correct way to make it work is to replace ListCons with

implicit def listCons[T]: Cons[List[T]] = new Cons[List[T]] {
    override def cons(t1: List[T], t2: List[T]): List[T] = t1 ++ t2
}