Scala 集合如何从映射操作中返回正确的集合类型?

Posted

技术标签:

【中文标题】Scala 集合如何从映射操作中返回正确的集合类型?【英文标题】:How are Scala collections able to return the correct collection type from a map operation? 【发布时间】:2011-07-09 04:49:43 【问题描述】:

注意:这是一个常见问题解答,专门询问,所以我可以自己回答,因为这个问题似乎经常出现,我想把它放在一个可以(希望)通过以下方式轻松找到的位置搜索

根据我的 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,所有这些都使用从TraversableLike继承的map操作,那么为什么它总是能够返回最具体的有效集合?甚至是String,它通过隐式转换提供map

【问题讨论】:

Scala 2.8 collections design tutorial的可能重复 一开始跳过了斜体部分,一时间陷入了深深的困惑。詹姆斯,链接的帖子中并没有真正回答这个具体问题。只是说隐含与它有关。假设凯文想要更精细,这不是重复。 @James - 这有点不同,我更专注于CanBuildFrom 机制 将其称为“能够返回最合适的集合”是相当牵强的。 @Tony:如果我们允许“适当”的定义与允许实体移动的上下文相关,那么该陈述将变得准确。 鉴于对集合的操作结果将是另一个集合,可以公平地说,如果可能的话,最“合适”的集合可能是另一个相同类型的集合,并且最具体的替代方案。不过,Logan 是对的,这应该是评论,而不是答案。 【参考方案1】:

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 类型参数(简称对于“表示”),以便他们知道实际操作的真正子类。所以例如TraversableLikeTraversable 相同,但在 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因此匹配BitSetInt这两种类型,所以查找会成功,推断的返回类型也会是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 时,它被初始化为 BitSetA 将匹配任何内容,因为canBuildFrom 使用A 类型参数化,在这种情况下,它被推断为String...因此产生Set[String] 的返回类型。

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

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

在范围内还有一个CanBuildFrom[String,Char,String],以便编译器知道当将String 的元素映射到Chars 时,返回类型也应该是一个字符串。从现在开始,使用相同的机制。

【讨论】:

+1 非常酷地解释了 Scala 的类型保留集合转换。此外,*** 的使用非常好。回到 Ye Olde Podcasts,Jeff 反复提到这个用例(将洞察力表达为一个问题,然后立即回答它),而我很少看到它。 @Jörg 然后更频繁地检查 Scala 问题。 :-) 我在某些情况下自己使用过它,我想我也看到其他人也这样做过。我想我们scalazzi喜欢解释事情! :-) 我一直在思考为什么尝试将breakOutIterator 转换为List 无法编译。在阅读了这篇文章和@DanielC.Sobral answer here 之后,我终于意识到a)我确实了解这一切是如何工作的,b)Iterator.map(...) 没有在第二个参数列表中使用隐式 CanBuildFrom ......为什么会这样? 【参考方案2】:

Architecture of Scala Collections 在线页面对基于 2.8 集合设计创建新集合的实际方面进行了详细说明。

引用:

“如果你想集成一个新的集合类,需要做些什么,以便它可以从正确类型的所有预定义操作中受益?在接下来的几页中,你将通过两个示例来完成此操作。 "

它使用一个用于编码 RNA 序列的集合和一个用于 Patricia trie 的集合作为示例。查找处理地图和朋友部分,了解如何返回适当的集合类型。

【讨论】:

以上是关于Scala 集合如何从映射操作中返回正确的集合类型?的主要内容,如果未能解决你的问题,请参考以下文章

11. Scala数据结构(下)-集合操作

Scala的集合

Scala从入门到精通之四-映射和元组

scala 数据结构(八 ):-map映射操作

Scala集合

Scala 系列—— 集合类型综述