Cannot get type of generic object in a list

雨燕双飞 提交于 2019-12-07 08:17:12

问题


I have the following trait:

trait Storage[C <: Config] {
  def get(name: String, version: Int): Option[C]
  def list: List[(String, String)]
  def register(config: C): Boolean
}

and I want to create the following class:

class MultiStorage[C <: Config](storages: List[Storage[_ <: C]]) extends Storage[C] {
    def get(name: String, version: Int): Option[C] = {...}
    def list: List[(String, String)] = {...}    
    def register(config: C) = {...}

If not clear, the meaning is that a MultiStorage stores elements of type C (or subtype) in several storages, each of them containing elements of a single type.

I am fighting with generics to implement the register method. The idea is that depending on the type of object I want to register, I need to choose the right storage to register on, something like:

def register(config: C) = {
   storages.foreach(s => {
      if (typeOf(s) is Storage[C]) { // same type of config
           s.register(config)
           return
      }
   }) 
}

I made several attempts with generics and type tags, however nothing useful to share here. I am probably thinking I need to add another type tag to distinguish what I receive in register and what is declared as type of the storage.

One of the ideas I tried was having a method in Storage that returns the type:

protected def getType()(implicit tag: TypeTag[C]): universe.Type = typeOf[C]

but on the caller side I was able to get as result something like _$1, which I honestly don't understand what it means.

Another try was to use shapeless, but in this case I am not sure if it is possible having a multi-storage containing an arbitrary number of elements in an HList storage


回答1:


The runtime class needs to be made available, for example:

class Config
class FooConfig extends Config
class BarConfig extends Config

trait Storage[C <: Config] {
    val ctag: ClassTag[C]

    def get(name: String, version: Int): Option[C]
    def list: List[(String, String)]
    def register(config: C): Boolean
}

class FooStorage(implicit val ctag: ClassTag[FooConfig]) extends Storage[FooConfig] {
    override def get(name: String, version: Int): Option[FooConfig] = ???

    override def list: List[(String, String)] = ???

    override def register(config: FooConfig): Boolean = ???
}

class BarStorage(implicit val ctag: ClassTag[BarConfig]) extends Storage[BarConfig] {
    override def get(name: String, version: Int): Option[BarConfig] = ???

    override def list: List[(String, String)] = ???

    override def register(config: BarConfig): Boolean = ???
}


class MultiStorage[C <: Config](storages: List[Storage[_ <: C]])(implicit val ctag: ClassTag[C]) extends Storage[C] {
    def get(name: String, version: Int): Option[C] = ???

    def list: List[(String, String)] = ???

    def register(config: C): Boolean = {
        storages.foreach(storage => {
            if (storage.ctag.runtimeClass.isAssignableFrom(config.getClass)) {

            }
        })
        ???
    }
}

As traits can't have constructor parameters, the implicit class tag needs to be repeated in every class implementing the trait. If your class structure allows Storage to be an abstract class instead, the amount of boilerplate can be reduced:

abstract class Storage[C <: Config](implicit val ctag: ClassTag[C]) {

    def get(name: String, version: Int): Option[C]
    def list: List[(String, String)]
    def register(config: C): Boolean
}

class FooStorage extends Storage[FooConfig] {
    override def get(name: String, version: Int): Option[FooConfig] = ???

    override def list: List[(String, String)] = ???

    override def register(config: FooConfig): Boolean = ???
}



回答2:


Possible approach with Shapeless is

import shapeless.{::, HList, HNil}

object App {
  trait Config
  object config1 extends Config
  object config2 extends Config

  trait Storage[C <: Config] {
    def get(name: String, version: Int): Option[C]
    def list: List[(String, String)]
    def register(config: C): Boolean
  }

  object storage1 extends Storage[config1.type] {
    override def get(name: String, version: Int): Option[config1.type] = ???
    override def list: List[(String, String)] = ???
    override def register(config: config1.type): Boolean = {
      println("storage1#register")
      true
    }
  }
  object storage2 extends Storage[config2.type] {
    override def get(name: String, version: Int): Option[config2.type] = ???
    override def list: List[(String, String)] = ???
    override def register(config: config2.type): Boolean = {
      println("storage2#register")
      true
    }
  }

  class MultiStorage[L <: HList](storages: L) /*extends Storage[C]*/ {
//    def get(name: String, version: Int): Option[C] = ???
    def list: List[(String, String)] = ???
    def register[C <: Config](config: C)(implicit find: Find[C, L]): Boolean = find(config, storages).register(config)
  }

  trait Find[C <: Config, L <: HList] {
    def apply(config: C, l: L): Storage[C]
  }

  trait LowPriorityFind {
    implicit def tail[C <: Config, L <: HList, C1 <: Config, T <: HList](implicit
      ev: L <:< (Storage[C1] :: T),
      find: Find[C, T]): Find[C, L] = (config, l) => find(config, l.tail)
  }

  object Find extends LowPriorityFind {
    implicit def head[C <: Config, L <: HList, T <: HList](implicit 
      ev: L <:< (Storage[C] :: T)): Find[C, L] = (_, l) => l.head
  }

  val multiStorage = new MultiStorage(storage1 :: storage2 :: HNil)

  def main(args: Array[String]): Unit = {
    multiStorage.register(config1) // storage1#register
    multiStorage.register(config2) // storage2#register
  }
}

Making MultiStorage extend Storage can be too restrictive. We could write

class MultiStorage(storages: List[Storage[_ <: Config]] /*i.e. List[Storage[T] forSome { type T <: Config}]*/) 
  extends Storage[T forSome { type T <: Config }] /*i.e. just Storage[Config]*/

i.e. Storage of some unknown type T <: Config. But since we can register configs of any type T <: Config it should be more like Storage[T forAll { type T <: Config }] or Storage[[T <: Config]T] if such syntax existed in Scala but actually Scala doesn't have rank-2 types.



来源:https://stackoverflow.com/questions/55079296/cannot-get-type-of-generic-object-in-a-list

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!