为什么PrefixMap ++ PrefixMap变得可变

时间:2019-01-09 15:03:05

标签: scala

在sbt控制台中的Scala中运行PrefixMap编程示例。

scala> PrefixMap("abc" -> 12, "abb" -> 13)
res0: PrefixMap[Int] = Map(abc -> 12, abb -> 13)

scala> PrefixMap("aaa" -> 15)
res1: PrefixMap[Int] = Map(aaa -> 15)

scala> res0 ++ res1
res2: scala.collection.mutable.Map[String,Int] = Map(abc -> 12, abb -> 13, aaa -> 15)

这个结果使我感到困惑。

我曾想过,当调用“ ++”方法时,“ ++”方法在对象PrefixMap中使用隐式canBuildFrom并创建了新的PrefixMap实例,但似乎从某个地方使用了另一个隐式值并创建了新的Map实例。

为什么res2类型不是PrefixMap? 还是我在某个地方犯了错误?

以下是PrefixMap示例中的代码。

import collection._
import scala.collection.mutable.{Builder, MapBuilder}
import scala.collection.generic.CanBuildFrom

object PrefixMap {
  def empty[T] = new PrefixMap[T]

  def apply[T](kvs: (String, T)*): PrefixMap[T] = {
    val m: PrefixMap[T] = empty
    for (kv <- kvs)
      m += kv

    m
  }

  def newBuilder[T]: Builder[(String, T), PrefixMap[T]] =
    new MapBuilder[String, T, PrefixMap[T]](empty)

  implicit def canBuildFrom[T]
    : CanBuildFrom[PrefixMap[_], (String, T), PrefixMap[T]] =
      new CanBuildFrom[PrefixMap[_], (String, T), PrefixMap[T]] {
        def apply(from: PrefixMap[_]) = newBuilder[T]
        def apply() = newBuilder[T]
      }
}

class PrefixMap[T] extends mutable.Map[String, T]
  with mutable.MapLike[String, T, PrefixMap[T]] {
  var suffixes: immutable.Map[Char, PrefixMap[T]] = Map.empty
  var value: Option[T] = None

  def get(s: String): Option[T] =
    if(s.isEmpty) value
    else          suffixes get (s(0)) flatMap (_.get(s substring 1))

  def withPrefix(s: String): PrefixMap[T] = {
    if(s.isEmpty) this
    else {
      val leading = s(0)
      suffixes get leading match {
        case None => suffixes = suffixes + (leading -> empty)
        case _    =>
      }
      suffixes(leading) withPrefix (s substring 1)
    }
  }

  override def update(s: String, elem: T) =
    withPrefix(s).value = Some(elem)

  override def remove(s: String): Option[T] =
    if(s.isEmpty) { val prev = value; value = None; prev}
    else suffixes get (s(0)) flatMap (_.remove(s substring 1))

  def iterator: Iterator[(String, T)] =
    (for (v <- value.iterator) yield ("", v)) ++
      (for ((chr, m) <- suffixes.iterator;
            (s, v) <- m.iterator) yield (chr +: s, v))

  def += (kv: (String, T)): this.type = { update(kv._1, kv._2); this }
  def -= (s: String): this.type = { remove(s); this }

  override def empty = new PrefixMap[T]
}

1 个答案:

答案 0 :(得分:4)

首先,重要的是要了解变量的类型与存储在该变量中的值的类型之间存在差异。在您的示例中,结果++的实际类型仍然是PrefixMap,但是变量的类型(即编译器可以证明的内容)仅为mutable.Map,这就是您在REPL中看到的。您可以通过打印res2.getClass以获取实际类型来轻松地进行验证。

我认为发生这种情况是因为Map实际上有两种不同的++方法:

  • 一个来自TraversabelLike,它是一个聪明的人,拥有CanBuildFrom和所有其他奇特的东西

  • 另一个来自scala.collection.MapLike的{​​{1}},在scala.collection.mutable.MapLike中被覆盖,它的通用性要差得多

 // collection.MapLike so here Map is collection.Map
 def ++[V1 >: V](xs: GenTraversableOnce[(K, V1)]): Map[K, V1]

 // mutable.MapLike so here the output Map is the mutable.map
 override def ++[V1 >: V](xs: GenTraversableOnce[(K, V1)]): Map[K, V1]

由于您的代码位于特定的上下文中,在该上下文中编译器确切地知道该类(与之类的通用.filter类似,必须使用TraversableLike基础结构),因此编译器使用更简单的{{ 1}}来自++

我不确定为什么其他mutable.MapLike方法首先存在。可能是为了支持与某些旧式设计的向后兼容性(Scala集合库已重新设计了几次)。但是,使这一系列方法(还有更多类似MapLike.++)工作的唯一方法是执行+所做的事情:即在mutable.MapLike定义中覆盖它们更具体的类型。另外请注意,PrefixMap在内部使用MapLike.++,因此最好使用它正确地使用或重新实现它。