Why is it safe not to check object-private or object-protected definitions for their variance position?

StackOverflow https://stackoverflow.com/questions/16428847

Question

I have learned that Scala does not check object-private(private[this]) or object-protected(protected[this]) definitions for their variance position. Why is it safe not to check them?

I have read some materials related to it but failed to find a complete answer yet. First, Odersky, et al.'s "Programming in Scala" says:

http://www.artima.com/pins1ed/type-parameterization.html#19.7

It turns out that accesses to variables from the same object in which they are defined do not cause problems with variance. The intuitive explanation is that, in order to construct a case where variance would lead to type errors, you need to have a reference to a containing object that has a statically weaker type than the type the object was defined with.

I am not sure about exactly what the authors mean by "a containing object" and "a statically weaker type". A code example would be more preferable here.

Second, "senia" gave a good example in Scala Modifiers and Type parametrization that shows what problem class-private (private) fields may have with variance. The example convinces me of why class-private members should be checked for their variance positions, but did not give an answer about the reason why we do not have to check object-private/protected definitions. I understand that such code as getOtherCache() cannot be written with object-private fields. But it does not give a proof of the innocuousness of object-private/projected definitions that we have not written a program that object-private/projected definitions cause problems with variance.

Third, Michid mentioned the topic briefly while decsribing how to implement function memoization in Scala:

http://michid.wordpress.com/2009/02/23/function_mem/

class Memoize1[-T, +R](f: T => R) extends (T => R) {
  ...
  private[this] val vals = mutable.Map.empty[T, R]
  ...
}

However since vals is accessed from its containing instance only, it cannot cause problems with variance.

Unfortunately it does not answer my question "how (or why) is access control related to variance?".

Can you explain in more detail the rationale of not checking object-private/protected definitions for their variance position (or provide some references)?

Was it helpful?

Solution

Problems with variance only occur once the compile-time and run-time type of an object differ:

val a: List[Any] = List("foo"): List[String]

Here a has a statically weaker type (List[Any]) than defined (List[String]). Further, the same applies to a containing object (i.e. an element of the list). (Static type Any, defined type String).

If we have object-private (or object-protected) fields, this situation cannot occur:

trait A[-T] {
  protected[this] val x: T
}

When we access x we can be sure it is actually of type T, even if A is contra-variant, since the this reference cannot be up-cast somewhere. (We always fully know our self-type).

So when we get back to Odersky's words here, let's have a look at:

val cont: A[String] = new A[Any] { ... }
cont.x // BAD! Is `Any`, not `String`

cont is a reference to an object that has statically weaker type than defined with, that's why the reference to x is not allowed. This up-cast situation will not happen with the this pointer, hence the following is OK:

trait B[-T] extends A[T] {
  def foo() {
    // here, this has always the most specific type.
    val tmp: T = this.x
    // do stuff
  }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top