Scala集合如何从map操作中返回正确的集合types?

注意:这是一个常见问题,具体询问我可以自己回答,因为这个问题似乎经常出现,我想把它放在一个可以通过search轻松find的地方

正如我在这里回答评论的提示


例如:

"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中,所有这些都使用从TraversableLikeinheritance的map操作,那么如何才能始终返回最具体的有效集合呢? 即使是通过隐式转换提供map String

斯卡拉集合是聪明的事情…

collections图书馆的内部是斯卡拉土地更先进的主题之一。 它涉及更高级的types,推理,变异, CanBuildFromCanBuildFrom机制 – 所有这些都使得从面向用户的angular度来看,它变得非常通用,易于使用和强大。 从APIdevise者的angular度理解它并不是一个初学者所要做的轻松任务。

另一方面,在这个深度上你真的需要使用集合来工作是非常罕见的。

所以让我们开始吧

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

List为例,这inheritance了(依次)

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

这是相当less的! 那么为什么这个深层次的? 简单地忽略XxxLike特性,该层次结构中的每一层都增加了一点function,或提供了一个更优化的inheritancefunction版本(例如,在Traversable上通过索引获取元素需要drop操作和head操作的组合,严重效率低下在索引序列上)。 在可能的情况下,尽可能地将所有function推到层次结构上,最大化可以使用它的子类的数量并消除重复。

map就是这样一个例子。 这个方法是在TraversableLike中实现的(虽然XxxLike特性对于图书馆devise人员来说确实是存在的,所以通常被认为是一个在大多数意图和目的的Traversable上的方法 – 我会很快来到这个部分),并被广泛的inheritance。 可以在某个子类中定义一个优化版本,但它仍然必须符合相同的签名。 考虑下面的用法(也在问题中提到):

 "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] 

在任何情况下,尽可能输出与inputtypes相同。 如果不可能,则检查inputtypes的超类,直到find提供有效返回types的超类。 正确的做了很多工作,特别是当你认为String甚至不是一个集合时,它可以被隐式地转换为一个集合。

那么怎么做呢?

谜题的一半是XxxLike特征(我曾经说过我会find他们……),其主要function是采用Reprtypes参数(简称为“表示”),以便他们知道真正的子类实际上正在操作。 因此,例如TraversableLikeTraversable相同,但是在Reprtypes参数上抽象化。 这个参数然后在拼图的后半部分使用。 CanBuildFromtypes类,用于捕获集合转换操作要使用的源集合types,目标元素types和目标集合types。

用一个例子来解释就更容易了!

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

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

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

这是巧妙的部分…范围内只有一个隐含的匹配前两个types参数。 第一个参数是Repr ,由XxxLike性状捕获,第二个参数是由当前收集特征(例如Traversable )捕获的元素types。 然后map操作也用一个types参数化,这个typesT是基于隐式定位的CanBuildFrom实例的第三个types参数推断的。 在这种情况下BitSet

因此, CanBuildFrom的前两个types参数是input,用于隐式查找,第三个参数是输出,用于推理。

CanBuildFromBitSet中的CanBuildFrom匹配BitSetInt两种types,所以查找会成功,并且推断的返回types也将是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是一个types别名,当BitSetSet派生时,它被初始化为BitSetA将匹配任何东西,因为canBuildFrom是用typesA参数化A ,在这种情况下,它被推断为String …因此产生返回typesSet[String]

所以为了正确的实现一个集合types,你不仅需要提供一个CanBuildFromtypes的正确的隐式types,还需要确保这个集合的具体types被作为Repr参数提供给正确的父特征(例如,在Map子类的情况下,这将是MapLike )。

String稍微复杂一些,因为它通过隐式转换来提供map 。 隐式转换为StringOps ,它的子类StringLike[String] ,最终派生TraversableLike[Char,String]StringReprtypes的参数。

还有一个范围内的CanBuildFrom[String,Char,String] ,以便编译器知道将String的元素映射到Char ,返回types也应该是一个string。 从这一点起,使用相同的机制。

Scala Collections在线页面的体系结构详细解释了基于2.8系列devise创build新系列的实际方面。

引用:

“如果你想整合一个新的集合类,需要做什么,以便它能从所有预定义的操作中获得正确的types?在接下来的几页中,你将通过两个例子来实现这一点。”

它使用例如编码RNA序列的集合和用于Patricia trie的集合。 寻找处理地图和朋友部分的解释如何做,以返回适当的集合types。