注意:这是一个常见问题解答,专门询问所以我可以自己回答,因为这个问题似乎经常出现,我想把它放在一个可以(希望)通过搜索轻松找到的位置
根据我的 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
上的索引获取元素需要 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]
在每种情况下,输出与输入的类型尽可能相同。如果不可能,则检查输入类型的父类(super class),直到找到提供有效返回类型的父类(super class)。要做到这一点需要大量的工作,尤其是当您考虑到
String
时。甚至不是一个集合,它只是隐式转换为一个。那么它是如何完成的呢?
拼图的一半是
XxxLike
特征(我确实说过我会找到它们...),其主要功能是获取 Repr
输入 param(“Representation”的缩写),以便他们知道实际操作的真正子类。所以例如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
捕获trait,第二个是元素类型,由当前集合 trait 捕获(例如 Traversable
)。 map
然后操作也被参数化为一个类型,这个类型 T
根据 CanBuildFrom
的第三个类型参数推断隐式定位的实例。 BitSet
在这种情况下。所以前两个类型参数为
CanBuildFrom
是输入,用于隐式查找,第三个参数是输出,用于推理。CanBuildFrom
在 BitSet
因此匹配两种类型 BitSet
和 Int
,所以查找会成功,推断的返回类型也是 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 是一个类型别名,它被初始化为
BitSet
当BitSet
源自 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/