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
类型参数然后这个参数将被谜题的后半部分使用;这CanBuildFrom
type 类,捕获源集合类型、目标元素类型和目标集合类型以供集合转换操作使用。
用例子来解释更容易!
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]
- String
是Repr
类型参数
还有一个CanBuildFrom[String,Char,String]
在范围内,以便编译器知道在映射 a 的元素时String
to Char
s,那么返回类型也应该是字符串。从此时开始,将使用相同的机制。