score:2

Accepted answer

this is because as soon as the type member check escapes to the outside, it can immediately appear both in co- and contravariant positions in functions and methods:

class bad[+a] {
  type check[b]
}

class b
class a extends b

val bad: bad[a] = ???
import bad._

def hypotheticalmethodsomewhereoutside1(c: check[b]) = ???
def hypotheticalmethodsomewhereoutside2(i: int): check[b] = ???

the crucial difference to the [b >: a] type argument in the method check

def check[b >: a]: good[b] = this

is that the single method check is under your control, you can guarantee that it does not use a in a not-covariant position.

in contrast to that, the type member check can appear in infinitely many other methods in both co- and contravariant positions, and those methods are not under your control, so you cannot prohibit the usage of check in positions in which a appears not-covariant, e.g. you cannot prevent hypotheticalmethodsomewhereoutsiden from the example above to be defined by someone else.


note that the presence of the method check is not necessary for your bad example to fail:

// error: covariant type a occurs in invariant position in type  >: a of type b
class bad[+a] {
  type check[b >: a] = bad[b]
  // def check[b >: a] : check[b] = this
}

however, if you hide the member type check from everyone (really everyone, including even other instances of same class, that is, with the extremely reclusive private[this] access modifier):

class bad[+a] {
  private[this] type check[b >: a] = bad[b]
}

it compiles just nicely.

the private[this] solves the problem, because with this modifier, only the methods inside the class have to be inspected, no hypothetical methods somewhere outside have to be taken into consideration:

class ok[+a] {
  private[this] type check[b >: a] = ok[b]
  def ok[b >: a](c: check[b]): unit = ???
  def alsook[b >: a]: check[b] = ???
}

note that the above can be written just as

class good[+a] {
  type check[+b] = good[b]
  def ok[b >: a](c: check[b]): unit = ???
  def alsook[b >: a]: check[b] = ???
}

which makes sense only if the type constructor good has way more arguments than check.


Related Query

More Query from same tag