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中,所有这些都使用从TraversableLike
inheritance的map
操作,那么如何才能始终返回最具体的有效集合呢? 即使是通过隐式转换提供map
String
。
斯卡拉集合是聪明的事情…
collections图书馆的内部是斯卡拉土地更先进的主题之一。 它涉及更高级的types,推理,变异, CanBuildFrom
和CanBuildFrom
机制 – 所有这些都使得从面向用户的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是采用Repr
types参数(简称为“表示”),以便他们知道真正的子类实际上正在操作。 因此,例如TraversableLike
与Traversable
相同,但是在Repr
types参数上抽象化。 这个参数然后在拼图的后半部分使用。 CanBuildFrom
types类,用于捕获集合转换操作要使用的源集合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,用于隐式查找,第三个参数是输出,用于推理。
CanBuildFrom
, BitSet
中的CanBuildFrom
匹配BitSet
和Int
两种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别名,当BitSet
从Set
派生时,它被初始化为BitSet
。 A
将匹配任何东西,因为canBuildFrom
是用typesA
参数化A
,在这种情况下,它被推断为String
…因此产生返回typesSet[String]
。
所以为了正确的实现一个集合types,你不仅需要提供一个CanBuildFrom
types的正确的隐式types,还需要确保这个集合的具体types被作为Repr
参数提供给正确的父特征(例如,在Map
子类的情况下,这将是MapLike
)。
String
稍微复杂一些,因为它通过隐式转换来提供map
。 隐式转换为StringOps
,它的子类StringLike[String]
,最终派生TraversableLike[Char,String]
– String
是Repr
types的参数。
还有一个范围内的CanBuildFrom[String,Char,String]
,以便编译器知道将String
的元素映射到Char
,返回types也应该是一个string。 从这一点起,使用相同的机制。
Scala Collections在线页面的体系结构详细解释了基于2.8系列devise创build新系列的实际方面。
引用:
“如果你想整合一个新的集合类,需要做什么,以便它能从所有预定义的操作中获得正确的types?在接下来的几页中,你将通过两个例子来实现这一点。”
它使用例如编码RNA序列的集合和用于Patricia trie的集合。 寻找处理地图和朋友部分的解释如何做,以返回适当的集合types。