2016-08-20 6 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を呼び出す「汎用」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 :: List [Int] = foo.keys.map(foo)(collection.breakOut)'はランタイム 'List(1、1)'になります。 – Kolmar

+0

コミュニティ回答オプションもありますが、私はそのキーストロークをわかりません。現時点で急ぐ必要があります。 –

5

foo.keysは戻り値の型がより一般的であるにもかかわらずSetを返し、Setの呼び出しマップは別のSetを生成します。推定されたコンパイルタイプまたはコンパイルタイプは、必ずしも最も正確なわけではありません。

あなたはSetkeysメソッドは、戻りタイプIterable[A]あるSetにもかかわらず、返すことがわかります。

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であることを認識することができます。これは妥当と聞こえます:セットは、値なしのMapとして見ることができます(javaのHashMap/HashSetもそうします)。 iterableに変換するとimplicitsが失われ、Setがなくなります(副次的に、CanBuildFromハックは堅牢ではなく、実際には既存のコレクションの拡張が複雑になるため、将来的には消えてしまう可能性があります)。this answerとコメント)。

Scalaは、Javaの概念を共有しています(de-jure and de-facto types)。 "de-jure"はメソッド定義で宣言されたものですが、 "事実上"は継承者の1つになるかもしれません。そのため、実際にSetMap.keySetから生成され、Setデジュアタイプを持つ)の場合、Map.keysタイプがIterableと表示されます。

最後に、fooのすべての値が同じで、Set(1,1)Set(1)になるため、最初のprintlnのが最後です。

+4

この回答は少し間違っています。これは暗黙の魔法ではなく、通常のOOP多態性魔法のようです。コンパイラは、 'foo.keys'が' Set'であると判断できません。それは 'Iterable':' Iterable.canBuildFrom'から 'CanBuildFrom'を使用しますが、' canBuildFrom'は実際のコレクションオブジェクトに対して 'genericBuilder'を呼び出します。これは実行時に' Set'なので ' Set.newBuilder'が使用されています。 – Kolmar

+1

また、CBFを廃止する計画はないと思います。あなたがリンクしている答えはその主張をサポートしていません。デフォルトではドキュメントに隠されるべきであり、それはすでに長い間実装されていると言われています。 – Kolmar

+0

@Kolmar "no CBF"についての私の推測は、新しいコレクションの提案に関する最近の議論に基づいています。そういうわけで、「行くかもしれない」。 – dveim

関連する問題