为伴随对象中的方法指定类型方差的正确方法是什么

时间:2011-03-11 02:21:35

标签: scala types covariance

对我来说,Scala类型系统中一个比较混乱的方面是理解协方差,逆变,类型界限等。

我正在尝试创建一个通用Repository特征,可以通过扩展Page特征的类的伴随对象扩展。我们的想法是,伴随对象将负责创建新实例等。如果在一段时间内未访问这些页面实例,则需要清理这些实例。因此,基类Repository特征会将它们注册到可以在后台actor线程中检查的存储库列表中。

以下是代码的精简版本。我在调用type mismatch时遇到register(pages)错误。编译器找到HashMap[String, T]但期待HashMap[String, Page]。我无法弄清楚如何使编译器满意。我可以将寄存器方法定义为def register[T <: Page](repo: HashMap[String, T) ...,但这只是将问题推迟到var repos的引用,这是我无法通用的资格。如果有人能够证明指定类型的正确方法,我将不胜感激。

编辑如果我将散列图声明为HashMap[String, Page],然后使用page转换从散列映射中检索到的page.asInstanceOf[String, T]值,我可以使其工作。有没有办法避免演员?

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: HashMap[String, Page]) {
    repos = repo :: repos
  }
  ...
}

class CoolPage extends Page

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

val p = CoolPage()

1 个答案:

答案 0 :(得分:4)

首先要注意的是,可变HashMap是不变的:class HashMap [A, B]。虽然不可变版本在值上是协变的:class HashMap [A, +B]

要注意的第二件事是你的repos变量意味着是一个多态集合,这意味着当你把东西放在那里时,一些编译时类型信息就会丢失。

但是由于您使用了可变HashMap,因为repos不变性,HashMap实际上不能是正确的多态集合。为了说明为什么让我们假设Page是一个类(所以我们可以立即它),我们在repos列表中放入一个HashMap [String,CoolPage]。然后我们可以这样做:

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

因此,编译器会给您一个错误,以保护您免受此攻击。

我想你可以通过使Repository协变来修复你的代码:

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()
    }
  }
}

repos更改为Repository[Page]的列表:

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

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

请记住,多态集合(如repos)会使您丢失元素的编译时类型信息:如果您在Repository[CoolPage]放置Repository[Page],则只会返回.asInstance[T]并且必须处理它

更新:通过Repository pagesprivate[this]代码中移除{{1}}。