【问题标题】:Type inference inconsistency with Any-value Map类型推断与 Any-value Map 不一致
【发布时间】:2018-03-29 20:53:08
【问题描述】:

在摆弄一个涉及使用任意类型值的 Map 的 sn-p 时,我遇到了一些类型推断不一致的问题:

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 以前一种方式组装:

  1. avMap 似乎已经捕获了相应类型标签中的所有推断类型。为什么有些人无法查找?
  2. 为什么在 Int/DoubleString/List[T] 之间查找类型的 Map 值不一致?也许,与 TypeTag 如何处理 AnyValAnyRef 下的类型有关?

我使用的是 Scala 2.11.12(和 2.12.x 似乎显示出同样的不一致)。提前致谢。

【问题讨论】:

    标签: scala type-inference type-erasure


    【解决方案1】:

    调用avMap.add(List(1, 2, 3)) 时推断的类型是scala.collection.immutable.List[Int]。但是当您调用avMap.grab[List[Int]] 时,将使用scala.List[Int] 类型。这个scala.Listpackage object scala 中定义的别名。

    Scala 理解这些类型是等价的:

    scala> typeOf[scala.collection.immutable.List[Int]] =:= typeOf[scala.List[Int]]
    res1: Boolean = true
    

    但是从TypeTag的角度来看它们仍然是不同的类型,并且它们的类型和类型标签具有不同的哈希码,因此它们是Map中的不同键:

    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$1(<console>:32)
      at scala.Option.getOrElse(Option.scala:121)
      at AnyValMap$.grab$extension(<console>:32)
      ... 31 elided
    

    【讨论】:

      猜你喜欢
      • 2016-07-27
      • 2013-03-25
      • 1970-01-01
      • 2021-03-24
      • 2020-03-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-01-12
      相关资源
      最近更新 更多