scala - Scala 集合如何能够从映射操作返回正确的集合类型?

标签 scala collections

注意:这是一个常见问题解答,专门询问所以我可以自己回答,因为这个问题似乎经常出现,我想把它放在一个可以(希望)通过搜索轻松找到的位置

根据我的 answer here 上的评论提示

例如:

"abcde" map {_.toUpperCase} //returns a String
"abcde" map {_.toInt} // returns an IndexedSeq[Int]
BitSet(1,2,3,4) map {2*} // returns a BitSet
BitSet(1,2,3,4) map {_.toString} // returns a Set[String]

查看scaladoc,所有这些都使用map操作继承自 TraversableLike ,那么它为什么总是能够返回最具体的有效集合?偶String ,提供 map通过隐式转换。

最佳答案

Scala 集合是聪明的东西......

集合库的内部结构是 Scala 领域中更高级的主题之一。它涉及到更高级的类型、推理、方差、隐式和 CanBuildFrom机制 - 从面向用户的角度来看,所有这些都是为了使它非常通用、易于使用且功能强大。从 API 设计者的角度理解它对于初学者来说并不是一项轻松的任务。

另一方面,您真正需要在这种深度处理集合是非常罕见的。

那么让我们开始吧……

随着 Scala 2.8 的发布,集合库被完全重写以消除重复,许多方法被移到一个地方,这样持续维护和添加新的集合方法会容易得多,但它也使层次结构更加困难了解。

List例如,这继承自(反过来)

  • LinearSeqOptimised
  • GenericTraversableTemplate
  • LinearSeq
  • Seq
  • SeqLike
  • Iterable
  • IterableLike
  • Traversable
  • TraversableLike
  • TraversableOnce

  • 这是相当多的!那么为什么会有这么深的层次结构呢?忽略 XxxLike简而言之,该层次结构中的每一层都添加了一点功能,或者提供了继承功能的更优化版本(例如,通过 Traversable 上的索引获取元素需要 drophead 操作的组合,在索引序列上效率非常低)。在可能的情况下,所有功能都尽可能地向上推,从而最大限度地增加可以使用它的子类的数量并消除重复。
    map只是一个这样的例子。该方法在TraversableLike中实现(虽然 XxxLike 特性只真正存在于库设计者中,所以它通常被认为是 Traversable 上的一种方法,用于大多数意图和目的 - 我很快就会谈到那部分),并且被广泛继承。可以在某些子类中定义优化版本,但它仍然必须符合相同的签名。考虑以下 map 的用法(正如问题中所提到的):
    "abcde" map {_.toUpperCase} //returns a String
    "abcde" map {_.toInt} // returns an IndexedSeq[Int]
    BitSet(1,2,3,4) map {2*} // returns a BitSet
    BitSet(1,2,3,4) map {_.toString} // returns a Set[String]
    

    在每种情况下,输出与输入的类型尽可能相同。如果不可能,则检查输入类型的父类(super class),直到找到提供有效返回类型的父类(super class)。要做到这一点需要大量的工作,尤其是当您考虑到 String 时。甚至不是一个集合,它只是隐式转换为一个。

    那么它是如何完成的呢?

    拼图的一半是 XxxLike特征(我确实说过我会找到它们...),其主要功能是获取 Repr输入 param(“Representation”的缩写),以便他们知道实际操作的真正子类。所以例如TraversableLikeTraversable 相同,但通过 Repr 抽象出来输入参数。然后这个参数被拼图的后半部分使用; CanBuildFrom捕获源集合类型、目标元素类型和目标集合类型以供集合转换操作使用的类型类。

    举个例子更容易解释!

    BitSet 定义了 CanBuildFrom 的隐式实例像这样:
    implicit def canBuildFrom: CanBuildFrom[BitSet, Int, BitSet] = bitsetCanBuildFrom
    

    编译时BitSet(1,2,3,4) map {2*} ,编译器将尝试隐式查找 CanBuildFrom[BitSet, Int, T]
    这是聪明的部分......在范围中只有一个与前两个类型参数匹配的隐式。第一个参数是 Repr ,由 XxxLike 捕获trait,第二个是元素类型,由当前集合 trait 捕获(例如 Traversable )。 map然后操作也被参数化为一个类型,这个类型 T根据 CanBuildFrom 的第三个类型参数推断隐式定位的实例。 BitSet在这种情况下。

    所以前两个类型参数为CanBuildFrom是输入,用于隐式查找,第三个参数是输出,用于推理。
    CanBuildFromBitSet因此匹配两种类型 BitSetInt ,所以查找会成功,推断的返回类型也是 BitSet .

    编译时BitSet(1,2,3,4) map {_.toString} ,编译器将尝试隐式查找 CanBuildFrom[BitSet, String, T] .对于 BitSet 中的隐式,这将失败,因此编译器接下来将尝试其父类(super class) - Set - 这包含隐含的:
    implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, Set[A]] = setCanBuildFrom[A]
    

    哪个匹配,因为 Coll 是一个类型别名,它被初始化为 BitSetBitSet源自 Set . A将匹配任何内容,如 canBuildFrom参数化类型为 A ,在这种情况下,它被推断为 String ...从而产生 Set[String] 的返回类型.

    所以要正确实现一个集合类型,你不仅需要提供一个正确的类型 CanBuildFrom 的隐式。 ,但您还需要确保该集合的具体类型作为 Repr 提供。 param 到正确的父特征(例如,这将是 MapLike 在子类化 Map 的情况下)。
    String有点复杂,因为它提供了 map通过隐式转换。隐式转换为 StringOps , 子类 StringLike[String] ,最终导出 TraversableLike[Char,String] - String正在Repr输入参数。

    还有一个CanBuildFrom[String,Char,String]在范围内,以便编译器知道在映射 String 的元素时至 Char s,则返回类型也应该是字符串。从这一点开始,使用相同的机制。

    关于scala - Scala 集合如何能够从映射操作返回正确的集合类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5200505/

    相关文章:

    object - typescript :定义类集合的类型

    java - 如何在 Kotlin 的 groupBy 中展平列表值列表

    windows - Windows 上的 sbt 0.13 - 无法访问存储库

    scala - 修复更高种类类型的类型推断

    arrays - Scala 中高效的二维数组列提取

    scala - Scala 范围内的剪辑编号

    ruby - 遍历 ruby​​ Hash 时并发修改

    c# - 如何检测集合在迭代过程中是否被修改

    arrays - 反转多维数组 - 函数式编程风格

    Java 等价于 scala.collection.mutable.Map.getOrElseUpdate