键入擦除,泛型和存在类型

时间:2015-05-02 21:28:47

标签: scala type-erasure

我有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,但我必须承认我迷路了。得到我想要的任何快捷方式?

1 个答案:

答案 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

编辑:要从评论中解答您的问题:

是必要的asInstanceOf吗? ......我的印象是,不得不诉诸它是一种不好的做法/要避免/不安全。这是对的吗?

由于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) } 的类型为lList[Field[_]]的类型为a,这意味着Field[_]具有存在类型,即编译器不具有知道它是a.value类型。但是,由于Aa.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]的任何通用版本匹配。我会说这个变体更糟糕,因为它使显式类型转换隐式,因此这个代码更容易出错。

我试图使用TypeTag []和typeOf []而不是ClassTag和runtimeClass,就像我在您链接的文档页面(以及其他地方)上找到的示例。有什么不同?最重要的是,我想要有关__ Field [_]的信息,那么为什么ClassTag会出现在A?!?

引入了三种类型标记作为Field的替换:

  • Manifest
  • TypeTag
  • WeakTypeTag

来自Scala documentation

  

ClassTags是scala.reflect.api.TypeTags #TypeTags的一个较弱的特殊情况,因为它们只包装给定类型的运行时类,而TypeTag包含所有静态类型信息。

虽然您可能也可以使用ClassTag来解决问题,但我认为比较运行时类就足够了。

问题是存在类型确实可以是TypeTag的任何超类,因为列表A可以包含所有类型的l。在我给出的示例中,我们有一个Field列表,因此在这种情况下,存在类型必须是Field[Int] :: Field[Int] :: Field[String] :: Field[Int]AnyInt的最不常见的超类型。换句话说:你从导出String的存在主义类型中得不到任何好处。

但是,您真正想要做的是找到列表中l: List[Field[_]]类型为value的第一个元素。因为A被删除,获取有关其运行时类型的信息的唯一方法是将信息作为附加参数传递,例如,使用隐式A证据。现在剩下的就是找出哪个元素具有相应类型的值,因此ClassTag