scala – 为什么这个Iterable在映射后产生一个Set?

前端之家收集整理的这篇文章主要介绍了scala – 为什么这个Iterable在映射后产生一个Set?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
在下面的示例代码中,为什么Iterable [String] test1在映射后生成一个Set?

val foo = Map("a" -> 1,"b" -> 1)
val test1: Iterable[String] = foo.keys
val test2: Iterator[String] = foo.keys.toIterator

println(test1.map(foo).size) // 1
println(test2.map(foo).size) // 2

我对此感到困惑,因为在阅读代码时它完全违反直觉.即使foo.keys只返回一个Iterable,它在调用map时会创建一个Set,因为反射代码显示

println(test1.map(foo).getClass.getName) // immutable.Set.Set1
println(test2.map(foo).getClass.getName) // Iterator$$anon$11

标准库如何确定它应该在这里创建一个immutable.Set,即使该集合的推断类型只是Iterable [String]?

解决方法

挖掘Kolmar的评论,虽然隐含的参数决定了如何构建结果集合,但在这种情况下,只需查询源集合以供构建器使用.

Iterable.map:

def map[B,That](f: (A) ⇒ B)(implicit bf: CanBuildFrom[Iterable[A],B,That]): That

隐式范围包括与类型args相关的类型,包括Iterable和Int.

Iterable定义了一个“泛型”CanBuildFrom,它在源集合上调用genericBuilder.这就是结果类型与源相关联的方式.

相反,结果集合通过采用CanBuildFrom [From = Nothing,_,_]与源分离.这就是cc.to [Set]的表达方式,其中构建Set而不考虑源集合cc.对于诸如map之类的操作,方法collection.breakOut提供了这样的CanBuildFrom,其中可以有效地推断出结果类型.

您可以为所需行为注入任意CanBuildFrom:

$scala
Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM,Java 1.8.0_92).
Type in expressions for evaluation. Or try :help.

scala> val m = Map("a" -> 1,"b" -> 1)
m: scala.collection.immutable.Map[String,Int] = Map(a -> 1,b -> 1)

scala> val k = m.keys
k: Iterable[String] = Set(a,b)

scala> import collection.{generic,mutable},generic.{CanBuildFrom => CBF},mutable.ListBuffer
import collection.{generic,mutable}
import generic.{CanBuildFrom=>CBF}
import mutable.ListBuffer

scala>   implicit def `as list`: CBF[Iterable[_],Int,List[Int]] =
     |     new CBF[Iterable[_],List[Int]] {
     |       def apply() = new ListBuffer[Int]
     |       def apply(from: Iterable[_]) = apply()
     |     }
as$u0020list: scala.collection.generic.CanBuildFrom[Iterable[_],List[Int]]

scala> k.map(m)
res0: List[Int] = List(1,1)

值得补充的是,完成可以显示2.11.8中的类型:

scala> k.map(m) //print<tab>

$line4.$read.$iw.$iw.k.map[Int,Iterable[Int]]($line3.$read.$iw.$iw.m)(scala.collection.Iterable.canBuildFrom[Int]) // : Iterable[Int]

使用breakOut:

scala> k.map(m)(collection.breakOut)
res1: scala.collection.immutable.IndexedSeq[Int] = Vector(1,1)

scala> k.map(m)(collection.breakOut) //print

$line4.$read.$iw.$iw.k.map[Int,scala.collection.immutable.IndexedSeq[Int]]($line3.$read.$iw.$iw.m)(scala.collection.`package`.breakOut[Any,scala.collection.immutable.IndexedSeq[Int]](scala.Predef.fallbackStringCanBuildFrom[Int])) // : scala.collection.immutable.IndexedSeq[Int]

如图所示,它实际上会为以下操作选择CanBuildFrom:

scala> "abc".map(_ + 1)
res2: scala.collection.immutable.IndexedSeq[Int] = Vector(98,99,100)

scala> "abc".map(_ + 1) //print

scala.Predef.augmentString("abc").map[Int,scala.collection.immutable.IndexedSeq[Int]](((x$1: Char) => x$1.+(1)))(scala.Predef.fallbackStringCanBuildFrom[Int]) // : scala.collection.immutable.IndexedSeq[Int]

相比:

scala> k.map(m)(collection.breakOut) : List[Int] //print

(($line6.$read.$iw.$iw.k.map[Int,List[Int]]($line5.$read.$iw.$iw.m)(scala.collection.`package`.breakOut[Iterable[String],List[Int]](scala.collection.immutable.List.canBuildFrom[Int]))): scala.`package`.List[scala.Int]) // : List[Int]

canonical Q&A on breakOut.

猜你在找的Scala相关文章