为什么这个Iterable映射后会产生一个Set?
Why does this Iterable produce a Set after mapping?
在下面的示例代码中,为什么 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
只是 returns 一个 Iterable,它在调用 map
时创建了一个 Set,如反射代码所示:
println(test1.map(foo).getClass.getName) // immutable.Set.Set1
println(test2.map(foo).getClass.getName) // Iterator$$anon
标准库如何确定它应该在这里创建一个 immutable.Set
,即使集合的推断类型只是 Iterable[String]
?
foo.keys
returns a Set
(尽管它的 return 类型更通用)并且在 Set
上调用 map 会产生另一个 Set
.推断或编译时类型并不总是最精确的。
您可以看到 Set
return 上的 keys
方法是 Set
,即使 return type 是 Iterable[A]
:
scala> Map(1 -> 2).keys
res0: Iterable[Int] = Set(1)
这是棘手的隐式魔法。简化答案:存在 CanBuildFrom
值,该值在隐式范围内传递。当编译器搜索最常见的类型时,它会在参数范围内寻找隐式。
在您的示例中,编译器能够找出 foo.keys
最常见的类型是 Set
。这听起来很合理:Set 可以看作是没有值的 Map(java 的 HashMap/HashSet 也是这样做的)。当您转换为可迭代对象时,隐含函数将丢失,并且 Set
消失(作为旁注,那些 CanBuildFrom
黑客并不健壮,并且将来可能会消失,因为它们确实使现有集合的扩展复杂化,您可能还想阅读 this 答案和评论)。
Scala 分享 Java 的 "de-jure and de-facto types" 概念。 "de-jure" 是在方法定义中声明的,但 "de-facto" 可能是继承者之一。这就是为什么,例如,您看到 Map.keys
类型为 Iterable
,而实际上它是 Set
(从 Map.keySet
产生,它有 Set
de-法律类型)。
最后,第一个 println 中的 1
是因为在基础映射 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
定义了一个 "generic" 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[_], 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
用于以下操作:
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: Char) => x.+(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.
在下面的示例代码中,为什么 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
只是 returns 一个 Iterable,它在调用 map
时创建了一个 Set,如反射代码所示:
println(test1.map(foo).getClass.getName) // immutable.Set.Set1
println(test2.map(foo).getClass.getName) // Iterator$$anon
标准库如何确定它应该在这里创建一个 immutable.Set
,即使集合的推断类型只是 Iterable[String]
?
foo.keys
returns a Set
(尽管它的 return 类型更通用)并且在 Set
上调用 map 会产生另一个 Set
.推断或编译时类型并不总是最精确的。
您可以看到 Set
return 上的 keys
方法是 Set
,即使 return type 是 Iterable[A]
:
scala> Map(1 -> 2).keys
res0: Iterable[Int] = Set(1)
这是棘手的隐式魔法。简化答案:存在 CanBuildFrom
值,该值在隐式范围内传递。当编译器搜索最常见的类型时,它会在参数范围内寻找隐式。
在您的示例中,编译器能够找出 foo.keys
最常见的类型是 Set
。这听起来很合理:Set 可以看作是没有值的 Map(java 的 HashMap/HashSet 也是这样做的)。当您转换为可迭代对象时,隐含函数将丢失,并且 Set
消失(作为旁注,那些 CanBuildFrom
黑客并不健壮,并且将来可能会消失,因为它们确实使现有集合的扩展复杂化,您可能还想阅读 this 答案和评论)。
Scala 分享 Java 的 "de-jure and de-facto types" 概念。 "de-jure" 是在方法定义中声明的,但 "de-facto" 可能是继承者之一。这就是为什么,例如,您看到 Map.keys
类型为 Iterable
,而实际上它是 Set
(从 Map.keySet
产生,它有 Set
de-法律类型)。
最后,第一个 println 中的 1
是因为在基础映射 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
定义了一个 "generic" 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[_], 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
用于以下操作:
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: Char) => x.+(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.