Cannot get type of generic object in a list

时间秒杀一切 提交于 2019-12-05 16:08:04

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 = ???
}

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.

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