Scala 集合是聪明的东西...
集合库的内部是 Scala 领域中更高级的主题之一。它涉及到更高级的类型、推理、方差、隐式和CanBuildFrom 机制——所有这些都是为了使其非常通用、易于使用并且从面向用户的角度来看功能强大。从 API 设计者的角度来理解它,对于初学者来说并不是一项轻松的任务。
另一方面,您实际上需要在这种深度使用集合是非常罕见的。
那么让我们开始吧……
随着 Scala 2.8 的发布,集合库被完全重写以消除重复,大量的方法被移动到一个地方,这样持续的维护和添加新的集合方法会容易得多,但这也使得层次结构更难理解。
以List为例,这个继承自(依次)
LinearSeqOptimised
GenericTraversableTemplate
LinearSeq
Seq
SeqLike
Iterable
IterableLike
Traversable
TraversableLike
TraversableOnce
这是相当少数!那么为什么会有这么深的层次结构呢?暂时忽略XxxLike 特征,该层次结构中的每一层都会添加一些功能,或者提供更优化的继承功能版本(例如,通过Traversable 上的索引获取元素需要drop 的组合和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]
在每种情况下,输出都尽可能与输入类型相同。如果不可能,则检查输入类型的超类,直到发现 确实 提供了有效的返回类型。要做到这一点需要做很多工作,尤其是当您考虑到 String 甚至不是一个集合,它只是隐式转换为一个。
那么它是怎么做的呢?
难题的一半是XxxLike 特征(我确实说我会找到它们...),其主要功能是采用Repr 类型参数(简称对于“表示”),以便他们知道实际操作的真正子类。所以例如TraversableLike 与 Traversable 相同,但在 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 特征捕获,第二个参数是元素类型,由当前集合特征捕获(例如Traversable)。然后map 操作也使用类型参数化,此类型T 是根据隐式定位的CanBuildFrom 实例的第三个类型参数推断的。 BitSet 在这种情况下。
所以CanBuildFrom 的前两个类型参数是输入,用于隐式查找,第三个参数是输出,用于推理。
BitSet中的CanBuildFrom因此匹配BitSet和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 派生自 Set 时,它被初始化为 BitSet。 A 将匹配任何内容,因为canBuildFrom 使用A 类型参数化,在这种情况下,它被推断为String...因此产生Set[String] 的返回类型。
所以要正确实现一个集合类型,你不仅需要提供一个正确的隐式类型CanBuildFrom,还需要确保该集合的具体类型作为Repr参数提供给正确的父特征(例如,在子类化Map 的情况下,这将是MapLike)。
String 稍微复杂一点,因为它通过隐式转换提供map。隐式转换为StringOps,它是StringLike[String] 的子类,最终派生TraversableLike[Char,String] - String 是Repr 类型参数。
在范围内还有一个CanBuildFrom[String,Char,String],以便编译器知道当将String 的元素映射到Chars 时,返回类型也应该是一个字符串。从现在开始,使用相同的机制。