Как правильно указать дисперсию типа для методов в сопутствующем объекте? - PullRequest
4 голосов
/ 11 марта 2011

Для меня одним из наиболее запутанных аспектов системы типов Scala является понимание ковариации, контравариантности, границ типов и т. Д.

Я пытаюсь создать общую черту Repository, которая может быть расширена объектами-компаньонами.объекты классов, которые расширяют черту Page.Идея состоит в том, что объект-компаньон будет отвечать за создание новых экземпляров и т. Д. Эти экземпляры страницы необходимо будет очистить, если к ним не было доступа в течение некоторого периода времени.Таким образом, базовая черта Repository зарегистрирует их в списке репозиториев, которые можно проверить в фоновом потоке актеров.

Ниже приведена урезанная версия кода.Я получаю type mismatch ошибку при звонке на register(pages).Компилятор обнаружил HashMap[String, T], но ожидает HashMap[String, Page].Я не могу понять, что нужно сделать, чтобы компилятор был счастлив.Я могу определить метод регистра как def register[T <: Page](repo: HashMap[String, T) ..., но это просто отодвигает проблему до ссылки на var repos, которую я не могу квалифицировать вообще.Я был бы признателен, если бы кто-то смог продемонстрировать правильный способ указания типов.

EDIT Я смогу заставить его работать, если объявить хэш-карту как 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 Ответ

4 голосов
/ 11 марта 2011

Первое, что нужно отметить, это то, что изменяемый HashMap является инвариантом: class HashMap [A, B]. Хотя неизменяемая версия ковариантна по значениям: class HashMap [A, +B].

Второе, на что следует обратить внимание, это то, что ваша переменная repos должна быть полиморфной коллекцией, что означает, что некоторая информация о типе времени компиляции теряется, когда вы помещаете туда вещи.

Но поскольку вы используете изменяемый HashMap, repos не может быть корректной полиморфной коллекцией из-за неизменности HashMap. Чтобы проиллюстрировать, почему давайте предположим, что Page является классом (чтобы мы могли мгновенно его использовать), мы поместили HashMap [String, CoolPage] в список repos. Тогда мы могли бы сделать это:

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] и вам придется иметь дело с этим.

обновление : удалено .asInstance[T] из кода Repository путем создания pages private[this].

...