Scala:使用具体类型实现 Map [英] Scala: implementing Map with concrete types

查看:45
本文介绍了Scala:使用具体类型实现 Map的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在 Scala 类型系统中遇到了某种怪癖,这让我有点困惑.我正在尝试创建一个扩展 Map[String,String] 的类,但我无法弄清楚如何以编译器接受它的方式实现 + 方法.

I'm running into some kind of quirk in the Scala type system that has me a bit stumped. I am trying to make a class that extends Map[String,String] and I can't quite figure out how to implement the + method in such a way that the compiler accepts it.

这是我现在拥有的代码:

Here's the code I have now:

class ParamMap(val pairs:List[(String,String)] = Nil) extends Map[String,String] {

  lazy val keyLookup = Map() ++ pairs

  override def get(key: String): Option[String] = keyLookup.get(key)
  override def iterator: Iterator[(String, String)] = pairs.reverseIterator

  /**
   * Add a key/value pair to the map
   */
  override def + [B1 >: String](kv: (String, B1)) = new ParamMap(kv :: pairs)

  /**
   * Remove all values for the given key from the map
   */
  override def -(key: String): ParamMap  = new ParamMap(pairs.filterNot(_._1 == key))

  /**
   * Remove a specific pair from the map
   */
  def -(kv: (String, String)) : ParamMap = new ParamMap(pairs - kv)
}

Scala 告诉我:

type mismatch;  found: (String, B1)  required: (String, String)

我相信这是因为 B1 被允许是 String 的子类型,但我的构造函数只需要一个 String (?).我最初的尝试是:

I believe this is because B1 is allowed to be a subtype of String but my constructor expects just a String (?). My original attempt was:

override def +(kv: (String, String)) = new ParamMap(kv :: pairs)

但这会抱怨,因为类型签名与特征不匹配:

But this complained because the type signature didn't match the trait:

class ParamMap needs to be abstract, since method + in trait Map of type [B1 >: String](kv: (String, B1))scala.collection.immutable.Map[String,B1] is not defined
method + overrides nothing

我是 Scala 的新手,我想我在这里对类型系统的工作方式感到困惑.也许我会尝试弄乱演员表,但我觉得可能有一种更好的方法",如果我知道的话,将来会为我省去很多麻烦.

I'm new to Scala and I think I'm getting over my head here in terms of how the type system works. Perhaps I'll try messing with casting but I have a feeling there might be a "better way" that, if I know it, will save me a lot of trouble in the future.

有什么想法吗?

推荐答案

关于 Scala 类型系统的一些背景.

Some background about Scala's type system.

  • 语法B1 >: String 意味着B1String超类型.所以 B1 不太具体,不能转换为 String.相反,B1 <: String 将是 子类型 关系.

  • The syntax B1 >: String means that B1 is a supertype of String. So B1 is less specific, and can't be cast to a String. Conversely, B1 <: String would be a subtype relationship.

Map trait 的定义是 Map [A​​, +B],其中 A 表示键的类型和 B 值的类型.+B 表示法表示 Map 在键类型中是 covariant,这意味着 T <: S暗示 Map[A, T] <: Map[A, S].

The definition of the Map trait is Map [A, +B], where A represents the type of the key and B the type of the value. The +B notation says that Map is covariant in the key type, which means that T <: S implies Map[A, T] <: Map[A, S].

Map.+ 方法的完整类型为 + [B1 >: B] (kv: (A, B1)): Map[A, B1].B 类的协方差迫使使用 B1 >: B.这是它如何工作的一个例子:给定一个地图 m: Map[String, String] 添加一个键值对,其类型不太具体 kv : (String, Any) 将导致不太具体的地图,(m + kv): Map[String, Any].

The full type of the Map.+ method is + [B1 >: B] (kv: (A, B1)): Map[A, B1]. The covariance of B kind of forces the use of B1 >: B. Here's an example of how it works: given a map m: Map[String, String] adding a key-value pair with a less specific type kv : (String, Any) will result in a less specific map, (m + kv): Map[String, Any].

最后一点说明了您的 ParamMap 定义的问题.根据 Map 接口,应该能够将 Any 类型的键添加到 ParamMap <: Map[String, String] 并返回一个 Map[String, Any].但是您试图定义 ParamMap.+ 以始终返回 ParamMap[String, String],这与 Map.+ 不兼容.

The last point illustrates the problem with your ParamMap definition. According to the Map interface, one should be able to add a key of type Any to a map of type ParamMap <: Map[String, String] and get back a Map[String, Any]. But you're trying to define ParamMap.+ to always return ParamMap[String, String], which is incompatible with Map.+.

解决问题的一种方法是给 ParamMap 一个明确的类型参数,比如(警告未经测试),

One way to fix the problem is to give ParamMap an explicit type parameter, something like (warning untested),

class ParamMap[B](val pairs:List[(String,String)] = Nil) extends Map[String, B] {
  ...
  override def + [B1 >: B](kv: (String, B1)) = new ParamMap[B1](kv :: pairs)
}

但这可能不是您想要的.我认为没有办法将值类型固定为 String 并实现 Map[String, String] 接口.

but this may not be what you want. I don't think there's a way to fix the value type as String and implement the Map[String, String] interface.

鉴于以上所有内容,为什么您的答案中的代码会编译?您实际上已经发现了 Scala 模式匹配的一个限制(不健全),它可能导致运行时崩溃.这是一个简化的示例:

Given all the above, why does the code in your answer compile? You've actually uncovered a limitation (unsoundness) of Scala's pattern matching, and it can lead to run-time crashes. Here's a simplified example:

def foo [B1 >: String](x: B1): Int = {
  val (s1: Int, s2: Int) = (x, x)
  s1
}

虽然可以编译,但它没有做任何有用的事情.事实上,它总是会因 MatchError:

Although this compiles, it doesn't do anything useful. In fact, it will always crash with a MatchError:

scala> foo("hello")
scala.MatchError: (hello,hello) (of class scala.Tuple2)
    at .foo(<console>:9)
    at .<init>(<console>:10)
    at .<clinit>(<console>)
    ...

在您的回答中,您基本上已经告诉编译器将 B1 实例转换为 String,如果转换不起作用,您将得到运行时崩溃.相当于一个不安全的强制转换,

In your answer, you've basically told the compiler to convert a B1 instance to a String, and if the conversion doesn't work, you'll get a runtime crash. It's equivalent to an unsafe cast,

(value: B1).asInstanceOf[String]

这篇关于Scala:使用具体类型实现 Map的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆