Notes on Variance
I’ve recently been working through the Rock The Jvm Cats course having previously completed the Scala Advanced course and being very impressed with his explanations and training materials.
Here are my take away notes on variance in Scala based on the session by Daniel Ciocîrlan.
package RockTheJvm.cats.chapter1
object TCVariance {
import cats.Eq
import cats.instances.int._ // Eq[Int] TC Instance
import cats.instances.option._ // construct a Eq[Option[Int]] TC Instance
import cats.syntax.eq._
val aCompare = Option(2) === Option(3)
val anInvCompare = Option(2) === None
// variance
class Animal
class Cat extends Animal
// covariant type: subtyping is propagated to the generic type
class Cage[+T]
val cage: Cage[Animal] = new Cage[Cat] // Cat <: Animal, so Cage[Cat] <: Cage[Animal]
// contravariant type: subtyping is propagated BACKWARDS to the generic type
class Vet[-T]
val vet: Vet[Cat] = new Vet[Animal] // Cat <: Animal then Vet[Animal] <: Vet[Cat]
// rule of thumb: "Has a T" = covariant, "Acts on T" = contravariant
// variance affects how TC instances are being fetched
// contravariant
trait SoundMaker[-T]
implicit object AnimalSoundMaker extends SoundMaker[Animal]
def makeSound[T](implicit soundMaker: SoundMaker[T]) = println("Wow")
makeSound[Animal] // OK - TC instance defined above
makeSound[Cat] // OK - TC instance for Animal is also applicable to Cats - like the vet example above
// rule 1: contravariant TCs can use the superclass instances if nothing is available strictly for that type
// has implications for subtypes
implicit object OptionSoundMaker extends SoundMaker[Option[Int]]
makeSound[Option[Int]]
makeSound[Some[Int]]
// covariant
trait AnimalShow[+T] {
def show: String
}
implicit object GeneralAnimalShow extends AnimalShow[Animal] {
override def show: String = "Animals Everywhere"
}
implicit object CatShow extends AnimalShow[Cat] {
override def show: String = "Cats everywhere"
}
def organiseShow[T](implicit event: AnimalShow[T]): String = event.show
// rule 2: covariant TCs will always use the more specific TC instance for that type
// but may confuse the compiler if the general TC is also present
// rule 3: you can't have both benefits
// Cats uses Invariant type classes
Option(2) === Option.empty[Int]
def main(args: Array[String]): Unit = {
println(organiseShow[Cat]) // ok - the compiler will inject CatShow as implicit
// println(organiseShow[Animal]) // will not compile - ambiguous values
}
}