如何使用固定类型参数子类化Scala immutable.Map?

时间:2011-05-05 19:36:19

标签: scala scala-collections

如果地图只能为其值存储一个不变的类型,我无法弄清楚如何在不可变的地图中处理覆盖“+”。

类似的东西:

class FixedMap(val impl : Map[String, Int])
    extends immutable.Map[String, Int] with immutable.MapLike[String, Int, FixedMap] {
    // This should return FixedMap if B1 is Int, and Map[String,B1]
    // if B1 is a superclass of Int; but there's no way to do that.
    // It is possible to return FixedMap here but then you have to
    // throw at runtime if B1 is not Int
    override def +[B1 >: Int](kv : (String, B1)) : Map[String, B1] = {
        kv match {
            case (k, v : Int) =>
                new FixedMap(impl + Pair(k, v))
            case _ =>
                impl + kv
        }
    }
    // ...
}

我希望这可以像使用CanBuildFrom的方法一样工作,并且如果可能的话始终保持原始类型。有办法吗?或者Map子类总是必须将值类型保留为类型参数吗?

这是一个完整的可编辑示例:

import scala.collection.immutable

// pointless class that wraps another map and adds one method
class FixedMap(val impl : Map[String, Int])
    extends immutable.Map[String, Int] with immutable.MapLike[String, Int, FixedMap] {

    override val empty : FixedMap = FixedMap.empty

    // This should return FixedMap if B1 is Int, and Map[String,B1]
    // if B1 is a superclass of Int; but there's no way to do that.
    // It is possible to return FixedMap here but then you have to
    // throw at runtime if B1 is not Int
    override def +[B1 >: Int](kv : (String, B1)) : Map[String, B1] = {
        kv match {
            case (k, v : Int) =>
                new FixedMap(impl + Pair(k, v))
            case _ =>
                impl + kv
        }
    }

    override def -(key : String) : FixedMap = {
        new FixedMap(impl - key)
    }

    override def get(key : String) : Option[Int] = {
        impl.get(key)
    }

    override def iterator : Iterator[(String, Int)] = {
        impl.iterator
    }

    def somethingOnlyPossibleOnFixedMap() = {
        println("FixedMap says hi")
    }
}

object FixedMap {
    val empty : FixedMap = new FixedMap(Map.empty)
}

object TestIt {
    val empty = FixedMap.empty
    empty.somethingOnlyPossibleOnFixedMap()
    val one = empty + Pair("a", 1)
    // Can't do the below because one is a Map[String,Int] not a FixedMap
    // one.somethingOnlyPossibleOnFixedMap()
}

3 个答案:

答案 0 :(得分:2)

以下是我的尝试:

class FixedMap(val impl: immutable.Map[String, Int])
    extends immutable.Map[String, Int] with immutable.MapLike[String, Int, FixedMap] {

  override def +[B1 >: Int](kv: (String, B1)): immutable.Map[String, B1] = impl + kv

  def +(kv: (String, Int))(implicit d: DummyImplicit): FixedMap = new FixedMap(impl + kv)

  // ...

}

你是对的:你不能覆盖已经存在的+,所以你必须把它留在那里 - 否则你的子类将无法做超类可以做的事情,这违反了{ {3}}。但是你可以添加一个额外的方法,你想要的确切参数(在这种特殊情况下你需要CanBuildFrom)。

唯一的问题是新方法与您尝试覆盖的方法具有完全相同的类型擦除,但具有不兼容的签名。要解决此问题,您可以将DummyImplicit - 在Predef中定义为一个隐式值始终可用的类。它的主要用途是在类似超载的情况下解决类型擦除问题。

请注意,要调用重载方法的地图的静态类型必须为FixedMap才能生效。如果对象的运行时类型为FixedType但是静态类型为常规Map[String, Int],则编译器将不会调用您的新重载方法。

答案 1 :(得分:1)

可以使用CanBuildFrom实现您想要的功能。问题是 - 你真的想要/需要做到吗?在SO中有几个类似的问题,这里有一个(希望你能在那里找到答案):

Extending Scala collections

通常,Scala会使用足够的工具来避免这种情况(扩展集合)。你真的需要一个很好的理由从这开始。

答案 2 :(得分:-1)

如果你添加另一个+重载,它看起来确实有效(我能说到目前为止最好):

def +(kv : (String, Int))(implicit bf : CanBuildFrom[FixedMap, (String, Int), FixedMap]) : FixedMap = {
    val b = bf(empty)
    b ++= this
    b += kv
    b.result
}

我之前看到的问题是由重写的+返回FixedMap引起的,从而阻止了对通用映射的任何升级。我想这允许该对的隐式转换为+'d工作。但是如果你修复了重写的+方法再次返回Map [String,B1],你就不能再对该对的值使用隐式转换了。编译器无法知道是否要转到超类映射,即Map [String,Any]或隐式转换为Int以便坚持使用FixedMap。有趣的是,方法的返回类型会改变是否使用隐式转换;我想给定一个FixedMap返回类型,编译器可以推断出B1总是只是B(或类似的东西!)。

无论如何这似乎是我的错误;你不能在pair._2上使用隐式转换,在与Map接口兼容的同时传递给+,甚至在概念上也是如此。现在我放弃了,我认为超载的+会正常工作。