2011-03-11 54 views
4

對我的Scala的類型系統的更混亂的一個方面是理解協方差,逆變,類型限制等什麼是指定型方差的陪伴方法的正確方式對象

我想創建一個通用的Repository特徵,可以通過擴展Page特徵的類的伴隨對象對象進行擴展。這個想法是,伴侶對象將負責創建新的實例等。如果這些頁面實例在一段時間內未被訪問,則需要對其進行清理。因此,基礎Repository特徵將把它們註冊到可以在後臺主角色線程中檢查的存儲庫列表中。

下面是代碼的簡化版本。在致電register(pages)時,我收到type mismatch錯誤。編譯器發現HashMap[String, T]但期待HashMap[String, Page]。我無法弄清楚如何讓編譯器感到滿意。我可以將註冊方法定義爲def register[T <: Page](repo: HashMap[String, T) ...,但這只是將問題推遲到我無法一般限定的var repos的引用。如果有人能夠證明指定類型的正確方法,我將不勝感激。

編輯我可以得到它,如果我宣佈HashMap中的HashMap[String, Page]再投從HashMap中提取出來的page.asInstanceOf[String, T]page值工作。有沒有辦法避免演員陣容?

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

回答

4

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

要注意的第二件事是,你的repos變量是一個多態集合,這意味着當你把東西放在那裏時,一些編譯時類型信息會丟失。

但是由於您使用可變的HashMap,因此HashMap不變,因此repos實際上不能成爲正確的多態集合。爲了說明爲什麼讓我們假設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] 

所以編譯器會給你一個錯誤來保護你免受這個錯誤。

我想你可以通過庫協修復代碼:

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]回來,並必須處理它。

更新:通過使pagesprivate[this]除去.asInstance[T]Repository代碼。

+0

很好的答案。我錯過了關於可變HashMap的不變性。衛生署!您的描述是現貨。我還喜歡'private [this]'的技巧,讓編譯器知道'pages'不會在任何可能導致差異問題的地方使用。感謝您的幫助。 – sellmerfud 2011-03-11 04:22:07

相關問題