我有List
参数类型List[Field[_]]
,我想要这样的东西:
sealed trait FieldType[K] { val name: String }
sealed trait StringField extends FieldType[String]
case object publisher extends StringField { val name = "publisher" }
// ...
trait Field[K] {
val field: FieldType[K]
val value: K
}
case class Publisher(value: String) extends Field[String] { val field = publisher }
// ...
def get[K](l: List[Field[_]], key: FieldType[K]) : Option[K] =
l match {
case Nil => None
case (a:Field[K]) :: rest => Some(a.value)
case (a:Field[_]) :: rest => get(rest, key)
}
不起作用,因为K被删除了。我尝试过typetags,但我必须承认我迷路了。得到我想要的任何快捷方式?
答案 0 :(得分:1)
是的,您可以使用类型标记让编译器生成已擦除的类型信息。根据{{3}},有三种方法来获取标签。
通过添加类型为ClassTag
的隐式证据参数,如果编译器找不到合适的隐式值,则会生成缺失的类型信息。根据证据,我们可以获取运行时类并将其与value
的{{1}}的运行时类进行比较。
Field
但请注意,在此示例中,def get[A](l: List[Field[_]])(implicit evidence: ClassTag[A]): Option[A] =
l match {
case Nil => None
case (a: Field[A]) :: rest if evidence.runtimeClass == a.value.getClass =>
Some(a.value)
case a :: rest =>
get[A](rest)
}
将生成一个get[Int]
,其中包含运行时类ClassTag
,而Int
将返回value.getClass
给定java.lang.Integer
,这将产生以下结果:
case class Other(value: Int) extends Field[Int]
请注意,我删除了参数def main(args: Array[String]): Unit = {
println(get[String](Other(4) :: Other(2) :: Publisher("foo") :: Nil)) // Some(foo)
println(get[Integer](Other(4) :: Other(2) :: Publisher("foo") :: Nil)) // Some(4)
println(get[Int](Other(4) :: Other(2) :: Publisher("foo") :: Nil)) // None
}
,因为它没有用处。如果密钥应该是唯一的,那么,我建议不要使用反射,而不是检查类型标签,而不是使用反射:
key
由于def get[A](l: List[Field[_]], key: FieldType[A]): Option[A] = l match {
case Nil => None
case a :: rest if a.field == key => Some(a.value.asInstanceOf[A])
case a :: rest => get(rest, key)
}
的类型为l
,List[Field[_]]
的类型为a
,这意味着Field[_]
具有存在类型,即编译器不具有知道它是a.value
类型。但是,由于A
和a.key
之间的关系,我们知道如果a.value
的类型为a.field
,则FieldType[A]
的类型为value
所以这个特定的类型转换是安全的(只要你不改变代码)。
绝对正确的是,必须使用类型转换表明设计存在缺陷,因此最好的解决方案可能是重新设计A
或列表Field
。实际上,我会问自己为什么需要在列表中放置各种不同类型的l
,然后再提取某种类型的Field
?也许Field
是错误的数据结构? List
是否是存储字段的更好选择?还是Map[FieldType[_], List[Field[_]]
?或者可能是自定义数据结构?
请注意,您可以通过以下方式摆脱Map[Class[_], List[Field[_]]
:
asInstanceOf
但这并没有为您提供更多的静态安全性,因为类型擦除会使def get[A](l: List[Field[_]], key: FieldType[A]): Option[A] = l match {
case Nil => None
case (a: Field[A]) :: rest if a.field == key => Some(a.value)
case a :: rest => get(rest, key)
}
与Field[A]
的任何通用版本匹配。我会说这个变体更糟糕,因为它使显式类型转换隐式,因此这个代码更容易出错。
引入了三种类型标记作为Field
的替换:
Manifest
TypeTag
WeakTypeTag
ClassTags是scala.reflect.api.TypeTags #TypeTags的一个较弱的特殊情况,因为它们只包装给定类型的运行时类,而TypeTag包含所有静态类型信息。
虽然您可能也可以使用ClassTag
来解决问题,但我认为比较运行时类就足够了。
问题是存在类型确实可以是TypeTag
的任何超类,因为列表A
可以包含所有类型的l
。在我给出的示例中,我们有一个Field
列表,因此在这种情况下,存在类型必须是Field[Int] :: Field[Int] :: Field[String] :: Field[Int]
,Any
和Int
的最不常见的超类型。换句话说:你从导出String
的存在主义类型中得不到任何好处。
但是,您真正想要做的是找到列表中l: List[Field[_]]
类型为value
的第一个元素。因为A
被删除,获取有关其运行时类型的信息的唯一方法是将信息作为附加参数传递,例如,使用隐式A
证据。现在剩下的就是找出哪个元素具有相应类型的值,因此ClassTag
。