在Scala Collections框架中,我认为使用时存在一些违反直觉的行为map()
.
我们可以区分(不可变)集合的两种转换。那些其实现调用newBuilder
重新创建结果集合,以及那些经历隐式CanBuildFrom
获得建造者。
第一类包含所有转换,其中所包含元素的类型不会改变。例如,他们是filter
, partition
, drop
, take
, span
等等。这些转换可以自由调用newBuilder
并重新创建与调用它们的集合类型相同的集合类型,无论多么具体:过滤List[Int]
总是可以返回一个List[Int]
;过滤一个BitSet
(或者RNA
中描述的示例结构本文介绍集合框架的架构) 总是可以返回另一个BitSet
(or RNA
)。让我们称他们为过滤变换.
第二类转型需要CanBuildFrom
为了更加灵活,因为所包含元素的类型可能会发生变化,因此,集合本身的类型可能无法重用:BitSet
不能包含String
s; an RNA
仅包含Base
s。这种转变的例子是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?!
现在,我明白为什么会这样了。已解释there and there。简而言之:隐含的CanBuildFrom
是基于静态类型插入的,并且取决于其实现def apply(from: Coll)
方法,可能能够也可能不能重新创建相同的集合类型。
现在我唯一的观点是,当我们知道我们正在使用映射操作生成一个集合时相同的元素类型(编译器可以静态确定),我们可以模仿过滤转换的工作方式并使用集合的本机构建器。我们可以重复使用BitSet
当映射到Int
s,创建一个新的TreeSet
具有相同的顺序,等等。
那么我们就可以避免这样的情况
for (i <- set) {
val x = i + 1
println(x)
}
不打印增加的元素TreeSet
按照相同的顺序
for (i <- set; x = i + 1)
println(x)
So:
- 您认为改变所描述的映射转换的行为是个好主意吗?
- 我严重忽视了哪些不可避免的警告?
- 如何实施?
我在想类似的事情implicit sameTypeEvidence: A =:= B
参数,可能有默认值null
(或者更确切地说implicit canReuseCalleeBuilderEvidence: B <:< A = null
),可以在运行时使用它来向CanBuildFrom
,这又可用于确定要返回的构建器的类型。