问题
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