Question

I studied exhaustively the compile rules for using co- and contra-variance using this book: http://www.cs.ucsb.edu/~benh/260/Programming-in-Scala.pdf (p 423-424)

I came up with an example which should not compile according to the rules, tried it and indeed, it does not compile due to co-variance/contra-variance issues. However, I don't see any logical reasons why it should not work. So as opposed to eg Array in Java, I think, you could not achieve any un-typesafe behaviour even if it compiled.

class TypeTest[+U] {
  def call(func: () => U): U = func()
}

object Main extends App {
    val test: TypeTest[Number] = new TypeTest[Integer]
    test.call(() => 3)
}

Compile output is:

Main.scala:2: error: covariant type U occurs in contravariant position in type () => U of value func
  def call(func: () => U): U = func()

Could you please make this clear to me?

Was it helpful?

Solution

This definition: class TypeTest[+U] means that, e.g., a TypeTest[Integer] is a valid substitute for a TypeTest[Number]. Therefore, as per the Liskov substitution principle, anything a TypeTest[Number] can do, a TypeTest[Integer] must be able to do as well. But this is clearly not the case here: if your code compiled, you could provide any Number to an instance that expects at least an Integer.

Although it may be tricky to understand these issues generally, you can trust the compiler on the co-/contravariance issues. It is because it can be quite hard for us to get right that the compiler does these extra checks. But if you really do want to get around the variance check, you can do it nevertheless:

class TypeTest[+U] {
  def call(func: Function0[U @uncheckedVariance]): U = func()
}

Example of why using this would be a bad idea

Because this would compile and throw at runtime:

trait Fruit
case class Orange(orangeKind: String) extends Fruit
case class Apple(appleKind: String) extends Fruit

def main(args: Array[String]) {
  val testApple = new TypeTest[Apple] {
    override def call(func: () => Apple) = func().copy(appleKind = "Different")
  }
  val testFruit: TypeTest[Fruit] = testApple

  testFruit.call(() => Orange("Round"))
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top