0

Given the following example code:

trait Gen[T, +R] {

  def follow[K](gen: Gen[R, K]): Gen[T, K]

  def i: T
  def j: R
}
case class GenImpl[T, R](i: T, j: R) extends Gen[T, R] {

  override def follow[K](gen: Gen[R, K]): Gen[T, K] = {
    GenImpl[T, K](this.i, gen.j)
  }
}

The compiler will give the following error:

Error:(9, 17) covariant type R occurs in invariant position in type Gen[R,K] of value gen
  def follow[K](gen: Gen[R, K]): Gen[T, K]
                ^

however, this code cannot possibly fail a type validation in runtime. The covariant of R simply means if:

R1 <: R, R2 <: R

Then:

Gen[T, R1] <:< Gen[T, R]

So a Gen[R, K] can be a parameter of .follow() of both Gen[T, R] and Gen[T, R1]. But Gen[R1, K] can only be a parameter of .follow() of Gen[T, R1], if applied on Gen[T, R2] or Gen[T, R] it will trigger a compilation error. There is no need to set R or R1 in Gen[R/R1, K] to be contravariant to do its job.

I can't see a case that can pass compilation and fail in runtime. What do you think? Is the compiler throwing a false alarm?

tribbloid
  • 4,026
  • 14
  • 64
  • 103
  • I fought several of these warnings in my time and found that the compiler was never wrong in that respect. Trust the compiler. :-) Here: you use `R` as first type parameter for `Gen`, which you yourself declared as invariant, so of course, the compiler won't be happy. – Jean-Philippe Pellet Jun 05 '16 at 21:31

1 Answers1

3

Suppose we have

class R0
class R1 extends R0
class T0

Then any instance of Gen[T0, R0] must offer the following service:

def follow[K](gen : Gen[R0, K]) : Gen[T0,K]

The variance annotation claims that Gen[T0, R1] <:< Gen[T0, R0]. So it must be substitutable and offer the same service. But Gen[T0, R1] actually offers the following service:

def follow[K](gen: Gen[R1, K]) : Gen[T0,K]

Suppose I had code like this:

def doFollow[K]( g : Gen[T0, R0], h : Gen[R0, K] ) : Gen[T0,K] = {
  g.follow( h )
}

Cool. Let's suppose I have some instances, constructed however:

val g0 : Gen[T0, R0]     = ???
val h0 : Gen[R0, String] = ???
val g1 : Gen[T0, R1]     = ???

I call doFollow[String]( g0 , h0 ) and everything works great, I get a result.

g1, I have claimed in my variance annotation, is substitutable for g0. So now I try doFollow[String]( g1 , h0 ). Oops. Then I must execute, in the body of the function,

g1.follow[String]( h0 )

but g1 only knows how to follow a Gen[R1, String]. h0 is a Gen[R0, String]. It is not true that Gen[R0, String] <:< Gen[R1, String], since the first parameter is declared invariant (it would have to be contravariant). So h0 is not an acceptable argument to g1's follow method, while it is an acceptable argument to g0's follow method. Things of g1's type, Gen[T0, R1], are not in fact substitutable for things of g0's type Gen[T0, R0].

But your variance annotation requires that Gen[T0, R1] objects can be substitutable for Gen[T0, R0]s. They cannot, and the compiler correctly called you on it.

Steve Waldman
  • 13,689
  • 1
  • 35
  • 45