Scala 为什么这个Iterable在映射后生成一个集合?

Scala 为什么这个Iterable在映射后生成一个集合?,scala,dictionary,collections,iterator,iterable,Scala,Dictionary,Collections,Iterator,Iterable,在下面的示例代码中,为什么Iterable[String]test1在映射后生成一个集合? 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 我对此感到

在下面的示例代码中,为什么Iterable[String]
test1在映射后生成一个集合?

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
时会创建一个集合,如反射代码所示:

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

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

foo.keys
返回一个
Set
(尽管其返回类型更一般)在
集合
上调用map会生成另一个
集合
。推断或编译时类型并不总是最精确的

您可以看到,
集合上的
方法返回一个
集合
,即使返回类型是
Iterable[a]

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

这是一种微妙的魔法。简化回答:存在
CanBuildFrom
值,该值在隐式范围内传递。当编译器搜索最常见的类型时,它会在参数的作用域中查找隐式类型

在您的示例中,编译器能够找出
foo.keys
最常见的类型是
Set
。这听起来很合理:可以将集合视为缺少值的映射(java的HashMap/HashSet也可以这样做)。当您转换为iterable时,隐式将丢失,
Set
也将消失(请注意,那些
CanBuildFrom
的hack并不健壮,将来可能会消失,因为它们确实会使现有集合的扩展变得复杂,您可能还需要阅读答案和注释)

Scala分享Java的“法律上和事实上的类型”概念。“法律上的”是在方法定义中声明的,但“事实上的”可能是继承者之一。这就是为什么,例如,您看到
Map.keys
类型为
Iterable
,而实际上它是
Set
(由
Map.keySet
生成,它具有
Set
法律类型)


最后,第一个println中的
1
是因为在底层map
foo
中,所有值都是相同的,
Set(1,1)
变成了
Set(1)

挖掘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,\uu,\uu]
与源代码分离。这就是
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]
如图所示,它实际上从
中提取用于以下操作的
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], Int, List[Int]](scala.collection.immutable.List.canBuildFrom[Int]))): scala.`package`.List[scala.Int]) // : List[Int]

如果是这样的话,我就不会感到困惑了。但事实上:foo.keySet返回一个集合。foo.keys返回一个Iterable。@Chris
Set
Iterable
的一个子类,所以
Map.keys
只调用
keySet
并将
集作为
Iterable
返回:我仍然不知道
Set.Map
产生一个Set的原因。像OP一样,我认为机制是用静态类型编码的。这个答案有点错误。这不是隐式魔术,更像是通常的OOP多态魔术。编译器无法判断
foo.keys
集合
。它使用
CanBuildFrom
来自
Iterable
Iterable.CanBuildFrom
,但是
CanBuildFrom
调用实际集合对象上的
genericBuilder
,并且,是的,这是运行时的
集合,这会导致使用
Set.newBuilder
,我不认为有任何让CBFs消失的计划。你链接到的答案不支持这种说法。它说默认情况下它们应该隐藏在文档中,这已经实施了很长时间了。@Kolmar我对“没有CBF”的猜测是基于最近关于新集合提案的讨论。这就是为什么“可能会消失”的原因。是这个提议吗?这很有趣,感谢您指出这一点。是的,这是一个建议。还值得补充的是,覆盖将类型绑定到源代码的常见机制是使用
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)
。还有一个社区答案选项,但我现在还不知道击键的方式。我现在得赶紧走了。
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]