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

For me, one of the most confusing aspects of a Scala type system is understanding covariance, contravariance, type restrictions, etc.

I am trying to create a generic Repository attribute that can be extended by objects of companion class objects that extend the 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 if they were not available for a certain period of time. Thus, the basic Repository attribute will register them in the list of repositories that can be checked in the background actor stream.

Below is a stripped down version of the code. I get a type mismatch error when calling register(pages) . The compiler found HashMap[String, T] , but expects 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 this just throws off the problem of the var repos link, which I cannot qualify as a whole. I would appreciate it if someone could demonstrate the correct way of specifying types.

EDIT . I can make it work by declaring the hash map as a HashMap[String, Page] and then applying the Page value obtained from the hash map using 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: HashMap[String, Page]) { repos = repo :: repos } ... } class CoolPage extends Page object CoolPage extends Page.Repository[CoolPage] { def newPage = new CoolPage } val p = CoolPage() 
+4
source share
1 answer

First of all, it should be noted that mutable HashMap is invariant: class HashMap [A, B] . Although the immutable version is covariant in value: class HashMap [A, +B] .

Secondly, your repos variable must be a polymorphic collection, which means that some information like compile time is lost when you put things in there.

But since you are using mutable HashMap , repos cannot actually be a valid polymorphic collection due to the invariance of HashMap . To illustrate why, let's assume that the page is a class (so we can create one), and we put the 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 think you can fix your code by making the 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 Repository[Page] list:

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

And remember that polymorphic collections (such as repos ) cause you to lose information about the types of compilation times of elements: if you put Repository[CoolPage] , you will only get Repository[Page] back and have to deal with it.

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

+4
source

Source: https://habr.com/ru/post/1343270/


All Articles