Notes on variance


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
  }
}