I'm creating a memoization class.
Each class memoizes a function type and has the following definition:
class MemoizedFunction1[-T1, +R](f: T1 => R) {
private[this] val cache = mutable.Map[T1, R]()
def apply(t: T1): R = cache.getOrElseUpdate(t,f(t))
}
This compiles nicely and works as expected.
However, if I remove the modified private[this]
I get the following error:
contravariant type T1 occurs in invariant position in type => scala.collection.mutable.Map[T1,R] of value cache
Why is that, when I remove the modifier, suddenly the contravariant type T1 interferes with the invariant type of the Map?
How do modifiers affect type parametrization?
Not that I understand all of it, but this is addressed in section 4.5 (Variance Annotations) of the Scala Language Specification 2.9 on page 45
References to the type parameters in object-private or object-protected values, variables, or methods (§5.2) of the class are not checked for their variance position. In
these members the type parameter may appear anywhere without restricting its legal variance annotations.
To simplify your example, according to the spec, this is fine:
class Inv[T]
class Foo[-T] {
private[this] val a: Inv[T] = sys.error("compiles")
protected[this] val b: Inv[T] = sys.error("compiles")
}
But if you remove [this]
it will complain. At some level it makes sense since if it is not object private or protected the contravariant return type could leak outside the object and cause a runtime error.
Let's assume you can remove [this]
.
Without [this]
you can add method getOtherCache
:
class MemoizedFunction1[-T1, +R](f: T1 => R) {
private val cache = mutable.Map[T1, R]() // trait Map[A, B] extends Iterable[(A, B)] with Map[A, B] with MapLike[A, B, Map[A, B]]
def apply(t: T1): R = cache.getOrElseUpdate(t,f(t))
def getOtherCache(other: MemoizedFunction1[T1, R]) {
val otherCache: mutable.Map[T1, R] = other.cache;
}
}
class A
class B extends A
val mf1: MemoizedFunction1[B, B] = new MemoizedFunction1[B, B](b => b)
val mf2: MemoizedFunction1[B, B] = new MemoizedFunction1[A, B](a => new B)
// mf2 is MemoizedFunction1[B, B]
// mf2 contains mutable.Map[A, B]
mf1.getOtherCache(mf2) //Error! mf2.cache is NOT mutable.Map[B, B]!
Programming in Scala touches on this topic in Section 19.7 Object private data: "object private members can be accessed only from within the object in which they are defined. It turns out that accesses to variables from the same object in which they are defined do not cause problems with variance."