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

2024-01-03

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

正如我的评论所提示的在这里回答 https://stackoverflow.com/questions/5189984/why-cant-this-simple-scala-example-be-compiled/5190950#5190950


例如:

"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 的发布,集合库被完全重写以消除重复,大量方法被移动到一个地方,以便持续维护和添加新的集合方法会变得更加容易,但这也使层次结构变得更加困难去理解。

Take List例如,这继承自(依次)

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

那可真是屈指可数啊!那么为什么会有这么深的层次结构呢?忽略XxxLike简而言之,该层次结构中的每一层都添加了一些功能,或者提供了继承功能的更优化版本(例如,通过索引来获取元素)Traversable需要组合drop and head操作,索引序列效率极低)。在可能的情况下,所有功能都尽可能地推向层次结构的最高层,从而最大限度地增加可以使用它的子类的数量并消除重复。

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]

在每种情况下,输出尽可能与输入具有相同的类型。当不可能时,将检查输入类型的超类,直到发现does提供有效的返回类型。要做到这一点需要做很多工作,尤其是当你考虑到这一点时String甚至不是一个集合,它只是隐式地转换为一个集合。

那么它是如何做到的呢?

谜题的一半是XxxLike特征(我did说我会去找他们...),其主要功能是采取Repr输入 param(“Representation”的缩写),以便他们知道实际正在操作的真正子类。所以例如TraversableLike是相同的Traversable,但抽象了Repr类型参数然后这个参数将被谜题的后半部分使用;这CanBuildFromtype 类,捕获源集合类型、目标元素类型和目标集合类型以供集合转换操作使用。

用例子来解释更容易!

BitSet 定义了一个隐式实例CanBuildFrom像这样:

implicit def canBuildFrom: CanBuildFrom[BitSet, Int, BitSet] = bitsetCanBuildFrom

编译时BitSet(1,2,3,4) map {2*},编译器将尝试隐式查找CanBuildFrom[BitSet, Int, T]

这是聪明的部分......作用域中只有一个隐式与前两个类型参数匹配。第一个参数是Repr,由捕获XxxLike特征,第二个是元素类型,由当前集合特征捕获(例如Traversable). The map然后操作也用一个类型参数化,这个类型T是根据第三个类型参数推断出来的CanBuildFrom隐式定位的实例。BitSet在这种情况下。

所以前两个类型参数CanBuildFrom是输入,用于隐式查找,第三个参数是输出,用于推理。

CanBuildFrom in BitSet因此匹配两种类型BitSet and Int,因此查找将成功,并且推断的返回类型也将是BitSet.

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

implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, Set[A]] = setCanBuildFrom[A]

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

所以要正确实现集合类型,你不仅需要提供正确的隐式类型CanBuildFrom,但您还需要确保该集合的具体类型作为Repr参数正确的父特征(例如,这将是MapLike在子类化的情况下Map).

String有点复杂,因为它提供了map通过隐式转换。隐式转换是StringOps,哪个子类StringLike[String],最终得出TraversableLike[Char,String] - StringRepr类型参数

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Scala 集合如何能够从映射操作返回正确的集合类型? 的相关文章

随机推荐