JaimeJorge
JaimeJorge

Reputation: 1895

Scala Modifiers and Type parametrization

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?

Upvotes: 13

Views: 718

Answers (3)

michid
michid

Reputation: 10824

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."

Upvotes: 2

senia
senia

Reputation: 38045

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]!

Upvotes: 7

huynhjl
huynhjl

Reputation: 41646

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.

Upvotes: 6

Related Questions