开发者

What is the correct way to specify type variance for methods in a companion object

For me one of the more confusing aspects of the Scala type system is understanding covariance, contravariance, type bounds etc.

I am trying to create a generic Repository trait that can be extended by companion objects objects of classes that extend a Page trait. The idea is that the companion object will be responsible for creating new instances etc. These page instances will need to be cleaned up if they haven't been accessed within some period of time. Thus the base Repository trait will register them in a list of repositories that can be checked in a background actor thread.

Below is a stripped down version of the code. I'm getting a type mismatch error on the call to register(pages). The compiler found HashMap[String, T] but is expecting HashMap[String, Page]. I can't figure out what to do to make the compiler happy. I can define the register method as def register[T <: Page](repo: HashMap[String, T) ... but that just defers the problem to the reference of the var repos which I cannot qualify generically. I would appreciate it if someone could demonstrate the correct way to specify the types.

EDIT I can get it to work if I declare the hashmap as HashMap[String, Page] and then cast the page value retrieved from the hashmap with page.asInstanceOf[String, T]. Is there a way to avoid the cast?

trait Page {
  val id = Random.hex(8)
  private var lastAccessed = new Date
  ...
}

object Page {
  import scala.collection.mutable.HashMap

  trait Repository[T <: Page] {
    private val pages = new HashMap[String, T]
    register(pages)

    def newPage: T

    def apply(): T = {
      val page = newPage
      pages(page.id) = page
      page
    }

    def apply(id: String): T = {
      pages.get(id) match {
        case Some(page) =>
          page.lastAccessed = now
          page
        case None =>
          this()
      }
    }
    ...
  }

  private var repos: List[HashMap[String, Page]] = Nil

  private def register(repo: Ha开发者_运维问答shMap[String, Page]) {
    repos = repo :: repos
  }
  ...
}

class CoolPage extends Page

object CoolPage extends Page.Repository[CoolPage] {
  def newPage = new CoolPage
}

val p = CoolPage()


First thing to note is that mutable HashMap is invariant: class HashMap [A, B]. Though the immutable version is covariant on values: class HashMap [A, +B].

Second thing to note is that your repos variable is meant to be a polymorphic collection which means that some compile time type information is lost when you put stuff there.

But since you use the mutable HashMap, the repos can't actually be a correct polymorphic collection because of HashMap invariance. To illustrate why let's suppose Page is a class (so we can instantitate it) and we put a HashMap[String, CoolPage] in the repos list. Then we could do this:

val m = repos.head // HashMap[String, Page]
m.put("12345678", new Page) // We just added a Page to HashMap[String, CoolPage]

So the compiler gives you an error to protect you from this.

I guess you can fix your code by making Repository covariant:

trait Repository[+T <: Page] {
  private[this] val pages = new HashMap[String, T]
  register(this)

  def newPage: T

  def apply(): T = {
    val page = newPage
    pages(page.id) = page
    page
  }

  def apply(id: String): T = {
    pages.get(id) match {
      case Some(page) =>
        page.lastAccessed = new Date
        page
      case None =>
        this()
    }
  }
}

And changing repos to be a list of Repository[Page]:

private var repos: List[Repository[Page]] = Nil

private def register(repo: Repository[Page]) {
  repos = repo :: repos
}

And remember that polymorphic collections (like repos) make you lose compile time type information of elements: if you put a Repository[CoolPage] there you only get Repository[Page] back and have to deal with it.

update: removed .asInstance[T] from the Repository code by making pages private[this].

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜