【问题标题】:Scala SortedMap.map method returns non-sorted map when static type is Map静态类型为 Map 时,Scala SortedMap.map 方法返回未排序的地图
【发布时间】:2012-09-27 20:08:25
【问题描述】:

我在使用 Scala 的 SortedMap[A,B] 时遇到了一些未经授权的奇怪现象。如果我声明对 SortedMap[A,B] "a" 的引用是 Map[A,B] 类型,那么对 "a" 的映射操作将产生一个未排序的映射实现。

例子:

import scala.collection.immutable._

object Test extends App {
    val a: Map[String, String] = SortedMap[String, String]("a" -> "s", "b" -> "t", "c" -> "u", "d" -> "v", "e" -> "w", "f" -> "x")
    println(a.getClass+": "+a)

    val b = a map {x => x}  // identity
    println(b.getClass+": "+b)
}

上面的输出是:

class scala.collection.immutable.TreeMap: Map(a -> s, b -> t, c -> u, d -> v, e -> w, f -> x)
class scala.collection.immutable.HashMap$HashTrieMap: Map(e -> w, f -> x, a -> s, b -> t, c -> u, d -> v)

身份转换前后键/值对的顺序不一样。

奇怪的是,从“a”中删除类型声明会使这个问题消失。这在一个玩具示例中很好,但会使 SortedMap[A,B] 无法用于传递给需要 Map[A,B] 参数的方法。

一般来说,我希望“map”和“filter”等高阶函数不会改变它们所应用的集合的基本属性。

有人知道为什么“地图”会这样吗?

【问题讨论】:

    标签: scala collections functional-programming higher-order-functions sortedmap


    【解决方案1】:

    map 方法与大多数收集方法一样,不是专门为SortedMap 定义的。它在更高级别的类 (TraversableLike) 上定义,并使用“构建器”将映射结果转换为正确的返回类型。

    那么它如何决定“正确”的返回类型是什么?好吧,它试图给你返回它开始时的返回类型。当你告诉 Scala 你有一个Map[String,String] 并要求它给map 时,构建器必须弄清楚如何“构建”返回的类型。由于您告诉 Scala 输入是 Map[String,String],因此构建器决定为您构建 Map[String,String]。构建器不知道您想要SortedMap,所以它没有给您。

    当你去掉Map[String,String] 类型注释时它起作用的原因是Scala 推断a 的类型是SortedMap[String,String]。因此,当您调用map 时,您是在SortedMap 上调用它,并且构建器知道构造一个SortedMap 以返回。

    就您断言方法不应改变“基本属性”而言,我认为您从错误的角度看待它。这些方法将始终为您返回一个符合您指定类型的对象。 type 定义了构建器的行为,而不是底层实现。当你这样想的时候,type 构成了方法应该如何表现的契约。

    我们为什么想要这个?

    为什么这是首选行为?让我们看一个具体的例子。假设我们有一个SortedMap[Int,String]

    val sortedMap = SortedMap[Int, String](1 -> "s", 2 -> "t", 3 -> "u", 4 -> "v")
    

    如果我使用修改键的功能来map 处理它,我会冒着在键冲突时丢失元素的风险:

    scala> sortedMap.map { case (k, v) => (k / 2, v) }
    res3: SortedMap[Int,String] = Map(0 -> s, 1 -> u, 2 -> v)
    

    但是,嘿,没关系。毕竟是Map,而且我知道这是Map,所以我应该预料到这种行为。

    现在假设我们有一个接受 Iterable 对的函数:

    def f(iterable: Iterable[(Int, String)]) = 
      iterable.map { case (k, v) => (k / 2, v) }
    

    由于此函数与Maps 无关,如果此函数的结果中的元素数少于输入,那将是非常令人惊讶的。毕竟,Iterable 上的 map 应该生成每个元素的映射版本。但是Map 一对Iterable,所以我们可以将它传递给这个函数。那么当我们这样做时,Scala 会发生什么?

    scala> f(sortedMap)
    res4: Iterable[(Int, String)] = List((0,s), (1,t), (1,u), (2,v))
    

    看那个!没有丢失任何元素!换句话说,Scala 不会因为违反我们对 map 上的 Iterable 应该如何工作的期望而让我们感到惊讶。如果构建器尝试根据输入是SortedMap 的事实来生成SortedMap,那么我们的函数f 会产生令人惊讶的结果,这会很糟糕。

    所以这个故事的寓意是:使用类型告诉集合框架如何处理您的数据。如果您希望您的代码能够预期地图已排序,则应将其键入为SortedMap

    【讨论】:

    • 嘿,感谢您将这种行为合理化。顺便说一句,使用 Iterable[(String,String)] 作为地图的静态类型被证明是一个有效的解决方案(在我的情况下),因为生成的集合保留了元素顺序。
    【解决方案2】:

    map的签名是:

    def map[B, That](f: ((A, B)) ⇒ B)(implicit bf: CanBuildFrom[Map[A, B], B, That]): That

    隐式参数bf 用于构建生成的集合。所以在你的例子中,由于a的类型是Map[String, String]bf的类型是:

    val cbf = implicitly[CanBuildFrom[Map[String, String], (String, String), Map[String, String]]]

    它只是构建了一个Map[String, String],它没有SortedMap 的任何属性。见:

    cbf() ++= List("b" -> "c", "e" -> "g", "a" -> "b") result

    欲了解更多信息,请参阅这篇优秀的文章:http://docs.scala-lang.org/overviews/core/architecture-of-scala-collections.html

    【讨论】:

      【解决方案3】:

      正如 dyross 所指出的,它是根据目标类型(通过 CanBuildFrom)选择的 Builder,它决定了您从 map 操作中获得的集合的类。现在这可能不是您想要的行为,但它确实允许您选择目标类型:

      val b: SortedMap[String, String] = a.map(x => x)(collection.breakOut) 
      

      breakOut 给出了一个通用的CanBuildFrom,其类型由上下文确定,即我们的类型注释。)

      因此您可以添加一些类型参数,允许您接受任何类型的 Map 或 Traversable(请参阅 this question),这将允许您在方法中执行映射操作,同时保留正确的类型信息,但您可以看看这并不简单。

      我认为更简单的方法是使用集合的mapflatMap 等方法来定义应用于集合的函数,而不是将集合本身​​发送到方法。

      即而不是

      def f[Complex type parameters](xs: ...)(complex implicits) = ...
      val result = f(xs)
      

      val f: X => Y = ...
      val results = xs map f
      

      【讨论】:

        【解决方案4】:

        简而言之:您将a 明确声明为Map 类型,并且Scala 集合框架非常努力地尝试使mapfilter 等高阶函数不改变集合的基本属性它们被应用于,因此它也会返回一个Map,因为这是你明确告诉它你想要的。

        【讨论】:

          猜你喜欢
          • 2012-11-20
          • 1970-01-01
          • 2021-07-26
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多