首页 文章

Scala的map()在映射到相同类型时是否应该有不同的行为?

提问于
浏览
5

在Scala Collections框架中,我认为有些行为在使用 map() 时是违反直觉的 .

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

第一个类别包含所有转换,其中包含的元素的类型不会更改 . 例如,它们是 filterpartitiondroptakespan 等 . 这些转换可以自由调用 newBuilder 并重新创建与它们被调用的集合类型相同的集合类型,无论具体如何:过滤 List[Int] 总是可以返回 List[Int] ;过滤 BitSet (或this article on the architecture of the collections framework中描述的 RNA 示例结构)总是可以返回另一个 BitSet (或 RNA ) . 我们称之为过滤转换 .

第二类转换需要更灵活,因为所包含元素的类型可能会发生变化,因此,集合本身的类型可能无法重用:a2874144_不能包含 String ; RNA 仅包含 Base . 这种转换的例子是 mapflatMapcollectscanLeft++ 等 . 让我们称它们为映射转换 .

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

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?!

现在,我理解为什么这样的工作原理 . 它被解释为therethere . 简而言之:基于静态类型插入隐式 CanBuildFrom ,并且,根据其 def apply(from: Coll) 方法的实现,可能会也可能无法重新创建相同的集合类型 .

现在我的唯一观点是,当我们知道我们正在使用映射操作产生一个带有 same element type (编译器可以静态确定)的集合时,我们可以模仿过滤变换的工作方式并使用集合的本机构建器 . 我们可以在映射到 Int 时重用 BitSet ,创建一个具有相同排序的新 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 提供更多信息,这反过来又可用于确定构建器的类型回来 .

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 课这样的伴侣采取适当的规定,你可能会发现 mapfilter 都按你的意愿工作......?

相关问题