类型推断与 Any-value Map 不一致
Type inference inconsistency with Any-value Map
在摆弄一个涉及使用任意类型值映射的片段时,如下所示,我遇到了一些类型推断不一致的问题:
import reflect.runtime.universe.TypeTag
case class AnyValMap[K]( m: Map[(K, TypeTag[_]), Any] ) extends AnyVal {
def add[V](k: K, v: V)(implicit tag: TypeTag[V]) = this.copy(
m = this.m + ((k, tag) -> v)
)
def grab[V](k: K)(implicit tag: TypeTag[V]): V = m((k, tag)).asInstanceOf[V]
}
val avMap = AnyValMap[String](Map.empty).
add("a", 100).
add("b", "xyz").
add("c", 5.0).
add("d", List(1, 2, 3))
// avMap: AnyValMap[String] = AnyValMap( Map(
// (a,TypeTag[Int]) -> 100, (b,TypeTag[String]) -> xyz, (c,TypeTag[Double]) -> 5.0,
// (d,TypeTag[List[Int]]) -> List(1, 2, 3)
// ) )
avMap.grab[Int]("a")
// res1: Int = 100
avMap.grab[String]("b")
// java.util.NoSuchElementException: key not found: (b,TypeTag[String]) ...
avMap.grab[Double]("c")
// res3: Double = 5.0
avMap.grab[List[Int]]("d")
// java.util.NoSuchElementException: key not found: (d,TypeTag[scala.List[Int]]) ...
现在,如果我在 add[V]
中使用显式类型信息组装 Map,一切都会正常进行:
val avMap = AnyValMap[String](Map.empty).
add[Int]("a", 100).
add[String]("b", "xyz").
add[Double]("c", 5.0).
add[List[Int]]("d", List(1, 2, 3))
avMap.grab[Int]("a")
// res5: Int = 100
avMap.grab[String]("b")
// res6: String = xyz
avMap.grab[Double]("c")
// res7: Double = 5.0
avMap.grab[List[Int]]("d")
// res8: List[Int] = List(1, 2, 3)
我的问题是:avMap
以前一种方式组装:
avMap
似乎已经捕获了相应类型标签中的所有推断类型。为什么有些人查不到?
- 为什么在
Int/Double
和String/List[T]
之间查找类型的Map值不一致?也许,与 TypeTag 如何处理 AnyVal
与 AnyRef
下的类型有关?
我正在使用 Scala 2.11.12(而 2.12.x 似乎显示出同样的不一致)。提前致谢。
调用avMap.add(List(1, 2, 3))
时推断出的类型是scala.collection.immutable.List[Int]
。但是当您调用 avMap.grab[List[Int]]
时,将使用 scala.List[Int]
类型。此 scala.List
是 package object scala
中定义的别名。
Scala 理解这些类型是等价的:
scala> typeOf[scala.collection.immutable.List[Int]] =:= typeOf[scala.List[Int]]
res1: Boolean = true
但从TypeTag
的角度来看,它们仍然是不同的类型,它们的类型和类型标签具有不同的哈希码,因此它们是 Map
:[=32= 中的不同键]
scala> typeTag[scala.collection.immutable.List[Int]].hashCode()
res2: Int = 629297926
scala> typeTag[scala.List[Int]].hashCode()
res3: Int = 1684352762
同样的事情发生在 String
上,它是 scala.Predef
中定义的 java.lang.String
的别名。字符串常量的类型是java.lang.String
,但是当你使用String
类型作为类型参数时,它表示scala.Predef.String
.
无论如何,将类型存储为键的方法非常脆弱。您还会遇到子类型的问题。例如,map.add(Some(10))
推断类型为 Some[Int]
而不是预期的 Option[Int]
.
除非你真的想在同一个键下存储多个不同类型的值,否则我建议你将类型存储为值的一部分,并在检索时检查它是否符合请求的类型:
case class AnyValMap[K]( m: Map[K, (Type, Any)] ) extends AnyVal {
def add[V](k: K, v: V)(implicit tag: TypeTag[V]) = this.copy(
m = this.m + (k -> (tag.tpe, v))
)
def grab[V](k: K)(implicit tag: TypeTag[V]): V = {
val (tpe, value) = m(k)
if (tpe <:< tag.tpe) value.asInstanceOf[V]
else throw new NoSuchElementException(s"wrong type $tpe of value for key: $k")
}
}
这很好用,而且还允许通过它们的超类型获取值:
scala> val avMap = AnyValMap[String](Map.empty).add("f", Some("abc"))
scala> avMap.grab[AnyRef]("f")
res4: AnyRef = Some(abc)
scala> avMap.grab[Option[AnyRef]]("f")
res5: Option[AnyRef] = Some(abc)
scala> avMap.grab[Option[String]]("f")
res6: Option[String] = Some(abc)
scala> avMap.grab[Option[Int]]("f")
java.util.NoSuchElementException: wrong type scala.Some[java.lang.String] of value for key: f
at AnyValMap$.grab$extension(<console>:28)
... 31 elided
如果您想要在同一个键下有多个不同类型的值,您能做的最好的可能是通过一个键的所有值的序列进行线性搜索:
case class AnyValMap[K]( m: Map[K, Vector[(Type, Any)]] ) extends AnyVal {
def add[V](k: K, v: V)(implicit tag: TypeTag[V]) = this.copy(
m = this.m + (k -> (this.m.getOrElse(k, Vector.empty) :+ (tag.tpe, v)))
)
def grab[V](k: K)(implicit tag: TypeTag[V]): V = {
m(k).collectFirst {
case (tpe, value: V @unchecked) if tpe <:< tag.tpe => value
}.getOrElse(throw new NoSuchElementException(s"no suitable value for key: $k"))
}
}
scala> val avMap = AnyValMap[String](Map.empty).
add("a", List(1, 2, 3)).
add("a", Some("abc"))
scala> avMap.grab[Seq[Int]]("a")
res20: Seq[Int] = List(1, 2, 3)
scala> avMap.grab[Option[String]]("a")
res21: Option[String] = Some(abc)
scala> avMap.grab[String]("a")
java.util.NoSuchElementException: no suitable value for key: a
at AnyValMap$.$anonfun$grab$extension(<console>:32)
at scala.Option.getOrElse(Option.scala:121)
at AnyValMap$.grab$extension(<console>:32)
... 31 elided
在摆弄一个涉及使用任意类型值映射的片段时,如下所示,我遇到了一些类型推断不一致的问题:
import reflect.runtime.universe.TypeTag
case class AnyValMap[K]( m: Map[(K, TypeTag[_]), Any] ) extends AnyVal {
def add[V](k: K, v: V)(implicit tag: TypeTag[V]) = this.copy(
m = this.m + ((k, tag) -> v)
)
def grab[V](k: K)(implicit tag: TypeTag[V]): V = m((k, tag)).asInstanceOf[V]
}
val avMap = AnyValMap[String](Map.empty).
add("a", 100).
add("b", "xyz").
add("c", 5.0).
add("d", List(1, 2, 3))
// avMap: AnyValMap[String] = AnyValMap( Map(
// (a,TypeTag[Int]) -> 100, (b,TypeTag[String]) -> xyz, (c,TypeTag[Double]) -> 5.0,
// (d,TypeTag[List[Int]]) -> List(1, 2, 3)
// ) )
avMap.grab[Int]("a")
// res1: Int = 100
avMap.grab[String]("b")
// java.util.NoSuchElementException: key not found: (b,TypeTag[String]) ...
avMap.grab[Double]("c")
// res3: Double = 5.0
avMap.grab[List[Int]]("d")
// java.util.NoSuchElementException: key not found: (d,TypeTag[scala.List[Int]]) ...
现在,如果我在 add[V]
中使用显式类型信息组装 Map,一切都会正常进行:
val avMap = AnyValMap[String](Map.empty).
add[Int]("a", 100).
add[String]("b", "xyz").
add[Double]("c", 5.0).
add[List[Int]]("d", List(1, 2, 3))
avMap.grab[Int]("a")
// res5: Int = 100
avMap.grab[String]("b")
// res6: String = xyz
avMap.grab[Double]("c")
// res7: Double = 5.0
avMap.grab[List[Int]]("d")
// res8: List[Int] = List(1, 2, 3)
我的问题是:avMap
以前一种方式组装:
avMap
似乎已经捕获了相应类型标签中的所有推断类型。为什么有些人查不到?- 为什么在
Int/Double
和String/List[T]
之间查找类型的Map值不一致?也许,与 TypeTag 如何处理AnyVal
与AnyRef
下的类型有关?
我正在使用 Scala 2.11.12(而 2.12.x 似乎显示出同样的不一致)。提前致谢。
调用avMap.add(List(1, 2, 3))
时推断出的类型是scala.collection.immutable.List[Int]
。但是当您调用 avMap.grab[List[Int]]
时,将使用 scala.List[Int]
类型。此 scala.List
是 package object scala
中定义的别名。
Scala 理解这些类型是等价的:
scala> typeOf[scala.collection.immutable.List[Int]] =:= typeOf[scala.List[Int]]
res1: Boolean = true
但从TypeTag
的角度来看,它们仍然是不同的类型,它们的类型和类型标签具有不同的哈希码,因此它们是 Map
:[=32= 中的不同键]
scala> typeTag[scala.collection.immutable.List[Int]].hashCode()
res2: Int = 629297926
scala> typeTag[scala.List[Int]].hashCode()
res3: Int = 1684352762
同样的事情发生在 String
上,它是 scala.Predef
中定义的 java.lang.String
的别名。字符串常量的类型是java.lang.String
,但是当你使用String
类型作为类型参数时,它表示scala.Predef.String
.
无论如何,将类型存储为键的方法非常脆弱。您还会遇到子类型的问题。例如,map.add(Some(10))
推断类型为 Some[Int]
而不是预期的 Option[Int]
.
除非你真的想在同一个键下存储多个不同类型的值,否则我建议你将类型存储为值的一部分,并在检索时检查它是否符合请求的类型:
case class AnyValMap[K]( m: Map[K, (Type, Any)] ) extends AnyVal {
def add[V](k: K, v: V)(implicit tag: TypeTag[V]) = this.copy(
m = this.m + (k -> (tag.tpe, v))
)
def grab[V](k: K)(implicit tag: TypeTag[V]): V = {
val (tpe, value) = m(k)
if (tpe <:< tag.tpe) value.asInstanceOf[V]
else throw new NoSuchElementException(s"wrong type $tpe of value for key: $k")
}
}
这很好用,而且还允许通过它们的超类型获取值:
scala> val avMap = AnyValMap[String](Map.empty).add("f", Some("abc"))
scala> avMap.grab[AnyRef]("f")
res4: AnyRef = Some(abc)
scala> avMap.grab[Option[AnyRef]]("f")
res5: Option[AnyRef] = Some(abc)
scala> avMap.grab[Option[String]]("f")
res6: Option[String] = Some(abc)
scala> avMap.grab[Option[Int]]("f")
java.util.NoSuchElementException: wrong type scala.Some[java.lang.String] of value for key: f
at AnyValMap$.grab$extension(<console>:28)
... 31 elided
如果您想要在同一个键下有多个不同类型的值,您能做的最好的可能是通过一个键的所有值的序列进行线性搜索:
case class AnyValMap[K]( m: Map[K, Vector[(Type, Any)]] ) extends AnyVal {
def add[V](k: K, v: V)(implicit tag: TypeTag[V]) = this.copy(
m = this.m + (k -> (this.m.getOrElse(k, Vector.empty) :+ (tag.tpe, v)))
)
def grab[V](k: K)(implicit tag: TypeTag[V]): V = {
m(k).collectFirst {
case (tpe, value: V @unchecked) if tpe <:< tag.tpe => value
}.getOrElse(throw new NoSuchElementException(s"no suitable value for key: $k"))
}
}
scala> val avMap = AnyValMap[String](Map.empty).
add("a", List(1, 2, 3)).
add("a", Some("abc"))
scala> avMap.grab[Seq[Int]]("a")
res20: Seq[Int] = List(1, 2, 3)
scala> avMap.grab[Option[String]]("a")
res21: Option[String] = Some(abc)
scala> avMap.grab[String]("a")
java.util.NoSuchElementException: no suitable value for key: a
at AnyValMap$.$anonfun$grab$extension(<console>:32)
at scala.Option.getOrElse(Option.scala:121)
at AnyValMap$.grab$extension(<console>:32)
... 31 elided