2016-08-20 83 views
4

在下面的示例代码中,为什么Iterable [String] test1在映射之后产生Set?为什么这个Iterable在映射之后产生一个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会返回一个可迭代,它调用map当创建一组,作为反射代码所示:

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

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

回答

2

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

Iterable.map

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

隐范围包括与类型指定参数时,包括IterableInt类型。

Iterable定义了在源集合上调用genericBuilder的“generic”CanBuildFrom。这就是结果类型与源相关联的方式。

相反,结果集合通过采取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[_], Int, List[Int]] { 
    |  def apply() = new ListBuffer[Int] 
    |  def apply(from: Iterable[_]) = apply() 
    |  } 
as$u0020list: scala.collection.generic.CanBuildFrom[Iterable[_],Int,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, Int, scala.collection.immutable.IndexedSeq[Int]](scala.Predef.fallbackStringCanBuildFrom[Int])) // : scala.collection.immutable.IndexedSeq[Int] 

如图所示,它实际上是拾取CanBuildFrom int结束了对诸如操作:

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], Int, List[Int]](scala.collection.immutable.List.canBuildFrom[Int]))): scala.`package`.List[scala.Int]) // : List[Int] 

canonical Q&A on breakOut

+0

另外值得一提的是,覆盖将类型绑定到源代码的常用机制是使用'scala.collection.breakOut'。因此'foo.keys.map(foo)(collection.breakOut)'将产生Vector(1,1):scala.collection.immutable.IndexedSeq [Int]'。这也允许从结果中进行类型推断,所以'val l:List [Int] = foo.keys.map(foo)(collection.breakOut)'将产生运行时的'List(1,1)'。 – Kolmar

+0

还有一个社区答案选项,但我不知道击键的关键。现在必须赶快离开。 –

5

foo.keys返回一个Set(尽管其返回类型更一般),并调用Set上的地图产生另一个Set。推断或编译时间类型并不总是最精确的。

你可以看到,在Setkeys方法返回即使Set返回类型Iterable[A]

scala> Map(1 -> 2).keys 
res0: Iterable[Int] = Set(1) 
+0

如果是这样的话,它不会让我困惑。但事实上: foo.keySet返回一个Set。 foo.keys返回一个Iterable。 – Chris

+2

@Chris'Set'是'Iterable'的子类,所以'Map.keys'只是调用'keySet'并将'Set'作为'Iterable'返回:https://github.com/scala/scala/blob /v2.11.8/src/library/scala/collection/MapLike.scala#L192 – Kolmar

+1

我仍然不知道为什么'set.map'产生一个集合。就像OP一样,我认为机制是用静态类型编码的。 –

0

这是棘手隐含的魔力。简化答案:存在CanBuildFrom值,即在隐式范围内传递。当编译器搜索最常见的类型时,它会在参数范围内查找含义。

在你的例子中,编译器能够找出最常见的foo.keys类型是Set。这听起来很合理:Set可以被视为具有缺少值的Map(也是Java的HashMap/HashSet)。当你转换为可迭代的时候,implicits丢失了,并且Set不见了(注意,这些CanBuildFrom黑客并不健壮,并且将来可能会消失,因为它们确实使现有集合的扩展复杂化了,您可能还想阅读this答案和评论)。

Scala分享了Java的“合法和事实类型”的概念。 “de-jure”是在方法定义中声明的,但“事实上”可能是继承者之一。这就是为什么,例如,您看到Map.keys类型为Iterable,实际上它是Set(从Map.keySet生产,其中Set分类型)。

末,1在第一的println是因为在基础地图foo所有值都相同,Set(1,1)成为Set(1)

+4

这个答案有点不对。这不是隐含的魔法,更像通常的OOP多态魔术。编译器无法确定'foo.keys'是一个'Set'。它使用来自'Iterable'的'CanteildFrom':'Iterable.canBuildFrom',但是'canBuildFrom'在实际的集合对象上调用'genericBuilder',因为是的,这是运行时的'Set',这导致'正在使用Set.newBuilder'。 – Kolmar

+1

另外,我不认为有任何计划让CBF消失。您所链接的答案不支持该声明。它表示,它们应该隐藏在文档中,默认情况下已经实施了很长时间。 – Kolmar

+0

@Kolmar我对“没有CBF”的猜测是基于最近关于新系列的建议的讨论。这就是为什么“可能会消失”。 – dveim

相关问题