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

发布于 2024-11-05 05:15:00 字数 2298 浏览 2 评论 0原文

如果映射只能存储其值的不变类型,我无法弄清楚如何处理不可变映射中的重写“+”。

类似于:

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()
}

I can't figure out how to deal with overriding "+" in an immutable map if the map can only store an invariant type for its values.

Something like:

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
        }
    }
    // ...
}

I'd like this to work like the methods that use CanBuildFrom and always keep the original type if possible. Is there a way? Or do Map subclasses always have to leave the value type as a type parameter?

Here's a complete compilable example:

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()
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

月下凄凉 2024-11-12 05:15:00

这是我要尝试的:

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)

  // ...

}

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

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

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

Here's what I'd try:

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)

  // ...

}

You're right: you cannot override the + that already exists, so you have to leave it there — otherwise your subclass wouldn't be able to do things that the superclasses can do, which violates the Liskov substitution principle. But you can add an additional method wich the exact arguments that you want (and you don't need a CanBuildFrom in this particular case).

The only problem is that the new method has the exact same type erasure as the one you were trying to override, but which has an incompatible signature. To solve this, you can add the DummyImplicit — defined in Predef as a class for which an implicit value is always available. Its main use is to work around type erasure in case of overloading like this.

Note that the static type of the map on which you want to call your overloaded method has to be FixedMap for this to work. If an object has a run-time type of FixedType but is statically typed to a regular Map[String, Int], the compiler won't call your new overloaded method.

⊕婉儿 2024-11-12 05:15:00

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

扩展 Scala 集合

一般来说,Scala 提供了足够的工具来避免这种情况(扩展集合)。你确实需要一个充分的理由来开始这样做。

It's possible to implement what you want using CanBuildFrom. The question is - do you really want/need to make it? The are several similar questions in SO, here is one of them (hope you will find answer there):

Extending Scala collections

Generally Scala gives use enough tools to avoid this (extending collections). And you really need a good reason to start with this.

走野 2024-11-12 05:15:00

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

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

我之前看到这样做的问题是由从覆盖的 + 返回固定地图引起的,从而阻止了对通用地图的任何升级。我想这允许对 + 的隐式转换起作用。但是,如果您修复该重写的 + 方法以再次返回 Map[String,B1],则无法再对该对的值使用隐式转换。编译器无法知道是否转到超类映射(即 Map[String,Any])或隐式转换为 Int 以坚持使用 FixMap。有趣的是,无论是否使用隐式转换,方法的返回类型都会改变;我猜想给定一个固定映射返回类型,编译器可以推断出 B1 始终只是 B(或类似的东西!)。

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

It looks like it does work (best I can tell so far) if you add another + overload:

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

The problem I was seeing doing this before was caused by returning FixedMap from the overridden +, thus preventing any upgrade to a generic map. I guess this allowed implicit conversion of the pair being +'d to work. But if you fix that overridden + method to return Map[String,B1] again, you can't use implicit conversion on the pair's value anymore. No way for the compiler to know whether to go to a superclass map i.e. Map[String,Any] or implicitly convert to Int in order to stick with FixedMap. Interesting that the return type of the method changes whether implicit conversions are used; I guess given a FixedMap return type the compiler can deduce that B1 is always just B (or something like that!).

Anyway this seems to be my bug; you just can't use implicit conversion on the pair._2 passed to + while being compatible with the Map interface, even conceptually. Now that I gave up on that, I think the overloaded + will work OK.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文