Type class variance

To see how this combination works, let's imagine that our USB cables represent a hierarchy with a common ancestor:

abstract class UsbConnector
case class Usb(orientation: Boolean) extends UsbConnector
case class Lightning(length: Int) extends UsbConnector
case class UsbC[Kind](kind: Kind) extends UsbConnector

How will this affect our type class definition? Well, of course, our previous version with every subtype implemented separately will work fine. But what if we would like to provide a generic type class instance for the whole UsbConnector hierarchy as shown in the following example?

implicit val usbCable: Cable[UsbConnector] = new Cable[UsbConnector] {
override def connect(c: UsbConnector): Boolean = {
println(s"Connecting $c")
true
}
}

We would not be able to connect our cables anymore:

scala> connectCable(UsbC("3.1"))
^
error: could not find implicit value for evidence parameter of type Cable[UsbC[String]]

This happens because the definition of our type class is invariant—thus, we are expected to provide an instance of Cable[T] with T <:< UsbC[String]. Is the usbCable a good fit? It turns out that it is not because its return type is Cable[UsbConnector] and we're expected to provide a UsbC[String].

We can fix this in two ways, depending upon whether we want our type class to work the same way for any class hierarchy or whether each class hierarchy that needs general treatment has to define it separately.

In the first case, we need to make sure that the compiler understands the following:

Cable[UsbConnector] <:< Cable[UsbC[String]]

We can check that this is currently not the case in the REPL:

implicitly[Cable[UsbConnector] <:< Cable[UsbC[String]]]
^
error: Cannot prove that Cable[UsbConnector] <:< Cable[UsbC[String]]

But we already know what we need to change in order to make it pass—our Cable should become contravariant:

trait Cable[-C] {
def connect(c: C): Boolean
}

As soon as we have proper variation in the definition of the Cable, everything falls into place, and the compiler can resolve all required implicits:

scala> implicitly[Cable[UsbConnector] <:< Cable[UsbC[String]]]
res1: TypeClassVariance.Cable[TypeClassVariance.UsbConnector] <:< TypeClassVariance.Cable[TypeClassVariance.UsbC[String]] = generalized constraint

scala> connectCable(UsbC("3.1"))
Connecting UsbC(3.1)

Unfortunately, if we decide that we need a special handling just for some of the classes from our hierarchy, we won't be able to reuse our implementation by defining a more specific type class instance:

implicit val usbCCable: Cable[UsbC[String]] = new Cable[UsbC[String]] {
override def connect(c: UsbC[String]): Boolean = {
println(s"Connecting USB C ${c.kind}")
true
}
}

scala> connectCable(UsbC("3.1"))
Connecting UsbC(3.1)

This test shows that the generic instance is still used and that the specific one is ignored.

Luckily, we have another option that is feasible in the case that we can afford that each hierarchy takes care of subtyping resolution on its own. In this case, we keep our type class invariant but change the type class instance to be of a specific type instead of a general one:

implicit def usbPolyCable[T <: UsbConnector]: Cable[T] = new Cable[T] {
override def connect(c: T): Boolean = {
println(s"Poly-Connecting $c")
true
}
}

We need to change val to a def in order to be able to parameterize it. Our generalized constraint starts to fail again for the invariant type class:

scala> implicitly[Cable[UsbConnector] <:< Cable[UsbC[String]]]
^
error: Cannot prove that Cable[UsbConnector] <:< Cable[UsbC[String]].

Nevertheless, we can connect the cable:

scala> connectCable(UsbC("3.1"))
Poly-Connecting UsbC(3.1)

Now, the compiler is able to choose the most specific instance available for our type class! If we bring the definition of implicit val usbCCable back into scope, we'll see that the output changes:

scala> connectCable(UsbC("3.1"))
Connecting USB C 3.1

This shows how static overloading resolution works. But this is only part of the picture. Let's clarify how and where the compiler looks for implicits if it needs them.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.142.85