scalacovariancevariancecontravarianceinvariance

Scala type variance


I have the following code

class Person
class Warrior extends Person
trait Commander[A] {
  def giveOrder(to: A)
  def delegate(to: Commander[A])
}

val warCommander: Commander[Warrior] = new Commander[Warrior] {
  override def giveOrder(to: Warrior): Unit = ???
  override def delegate(to: Commander[Warrior]): Unit = ???
}

val president: Commander[Person] = new Commander[Person] {
  override def giveOrder(to: Person): Unit = ???
  override def delegate(to: Commander[Person]): Unit = ???
}

warCommander.giveOrder(new Person) // GOOD ERROR: Person is not a Warrior
president.giveOrder(new Person)
warCommander.delegate(president) // GOOD ERROR: Commander[Person] is not a Commander[Warrior]
president.delegate(warCommander) // UNWANTED ERROR: Commander[Warrior] is not a Commander[Person]

So the last error is not expected but if I make A to be covariant it errors out because then I could say

class Civilian extends Person
val a: Commander[Warrior] = new Commander[Person] { def giveOrder(to: Civilian) ...

which is very confusing, so I can accept that.

So at this point, how to remove the last error and let the compiler understand that a Warrior is a Person?


Solution

  • If your intention is for some type A to be able to delegate to A and all types B that are a subtype of A, then you can achieve that without covariance (note the def delegate[B <: A](to: Commander[B])):

    class Person
    class Warrior extends Person
    trait Commander[A] {
      def giveOrder(to: A)
      def delegate[B <: A](to: Commander[B])
    }
    
    val warCommander: Commander[Warrior] = new Commander[Warrior] {
      override def giveOrder(to: Warrior): Unit = ???
      override def delegate[B <: Warrior](to: Commander[B]): Unit = ???
    }
    
    val president: Commander[Person] = new Commander[Person] {
      override def giveOrder(to: Person): Unit = ???
      override def delegate[B <: Person](to: Commander[B]): Unit = ???
    }
    
    warCommander.giveOrder(new Person) // ERROR: Person is not a Warrior
    president.giveOrder(new Person)    // OK
    warCommander.delegate(president)   // ERROR: Commander[Person] is not a Commander[Warrior]
    president.delegate(warCommander)   // OK