对我来说,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()
答案 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
pages
从private[this]
代码中移除{{1}}。