2016-08-20 119 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

相關問題