5

在 Scala Collections 框架中,我认为在使用map().

我们可以区分(不可变)集合上的两种转换。那些实现调用newBuilder重新创建结果集合的人,以及那些通过隐式CanBuildFrom获取构建器的人。

第一类包含所有转换,其中包含元素的类型不会改变。例如,它们是 , filter, partition, drop,takespan。这些转换可以自由调用newBuilder和重新创建与调用它们的集合类型相同的集合类型,无论多么具体:过滤 aList[Int]总是可以返回 a List[Int]; 过滤一个BitSet(或本文中描述的关于集合框架架构RNA的示例结构)总是可以返回另一个(或)。我们称它们为过滤转换BitSetRNA

第二类转换需要CanBuildFroms 更加灵活,因为包含的元素的类型可能会发生变化,因此,集合本身的类型可能无法重用:aBitSet不能包含Strings;anRNA只包含Bases。此类转换的示例是map, flatMap, collect, scanLeft,++等。我们称它们为映射转换

现在这里是要讨论的主要问题。无论集合的静态类型是什么,所有过滤转换都会返回相同的集合类型,而映射操作返回的集合类型会因静态类型而异。

scala> import collection.immutable.TreeSet
import collection.immutable.TreeSet

scala> val treeset = TreeSet(1,2,3,4,5) // static type == dynamic type
treeset: scala.collection.immutable.TreeSet[Int] = TreeSet(1, 2, 3, 4, 5)

scala> val set: Set[Int] = TreeSet(1,2,3,4,5) // static type != dynamic type
set: Set[Int] = TreeSet(1, 2, 3, 4, 5)

scala> treeset.filter(_ % 2 == 0)
res0: scala.collection.immutable.TreeSet[Int] = TreeSet(2, 4) // fine, a TreeSet again

scala> set.filter(_ % 2 == 0)    
res1: scala.collection.immutable.Set[Int] = TreeSet(2, 4) // fine

scala> treeset.map(_ + 1)        
res2: scala.collection.immutable.SortedSet[Int] = TreeSet(2, 3, 4, 5, 6) // still fine

scala> set.map(_ + 1)    
res3: scala.collection.immutable.Set[Int] = Set(4, 5, 6, 2, 3) // uh?!

现在,我明白为什么会这样了。它在那里那里解释。简而言之:隐式CanBuildFrom是基于静态类型插入的,并且根据其def apply(from: Coll)方法的实现,可能会也可能不会重新创建相同的集合类型。

现在我唯一的一点是,当我们知道我们正在使用映射操作产生具有相同元素类型(编译器可以静态确定)的集合时,我们可以模仿过滤转换的工作方式并使用集合的本机构建器。我们可以BitSet在映射到Ints 时重用,创建一个TreeSet具有相同顺序的新对象,等等。

然后我们将避免以下情况

for (i <- set) {
  val x = i + 1
  println(x)
}

TreeSet以相同的顺序打印增加的元素

for (i <- set; x = i + 1)
  println(x)

所以:

  • 您认为按照所述更改映射转换的行为是一个好主意吗?
  • 我严重忽视了哪些不可避免的警告?
  • 如何实施?

我在考虑类似implicit sameTypeEvidence: A =:= B参数的东西,可能有一个默认值null(或者更确切地说是一个implicit canReuseCalleeBuilderEvidence: B <:< A = null),它可以在运行时用于为 提供更多信息CanBuildFrom,而这些信息又可以用来确定要返回的构建器的类型。

4

1 回答 1

1

我再次查看了它,我认为您的问题不是由 Scala 集合的特定缺陷引起的,而是由于TreeSet. 因为以下确实按预期工作:

val list = List(1,2,3,4,5)
val seq1: Seq[Int] = list
seq1.map( _ + 1 ) // yields List

val vector = Vector(1,2,3,4,5)
val seq2: Seq[Int] = vector
seq2.map( _ + 1 ) // yields Vector

所以原因是TreeSet缺少一个专门的伴随对象/构建器:

seq1.companion.newBuilder[Int]    // ListBuffer
seq2.companion.newBuilder[Int]    // VectorBuilder
treeset.companion.newBuilder[Int] // Set (oops!)

所以我的猜测是,如果你为你的班级适当地安排这样的同伴RNA,你可能会发现两者map并按filter你的意愿工作......?

于 2011-04-17T16:18:47.597 回答