Scala 2.8では、scala.collection.package.scala
にオブジェクトがあります:
def breakOut[From, T, To](implicit b : CanBuildFrom[Nothing, T, To]) =
new CanBuildFrom[From, T, To] {
def apply(from: From) = b.apply() ; def apply() = b.apply()
}
これは次の結果になると言われています。
> import scala.collection.breakOut
> val map : Map[Int,String] = List("London", "Paris").map(x => (x.length, x))(breakOut)
map: Map[Int,String] = Map(6 -> London, 5 -> Paris)
ここで何が起こっていますか? breakOut
がList
に対して引数としてと呼ばれるのはなぜですか?
答えは、map
の定義にあります。
def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That
2つのパラメーターがあることに注意してください。 1つ目は関数で、2つ目は暗黙的です。暗黙的に指定しない場合、Scalaは、利用可能な最も多くspecificを選択します。
breakOut
について
それで、breakOut
の目的は何ですか? 「文字列のリストを取得し、各文字列をタプル(Int, String)
に変換し、その中からMap
を生成する」という質問の例を考えてみましょう。これを行う最も明白な方法は、中間のList[(Int, String)]
コレクションを作成し、それを変換します。
map
がBuilder
を使用して結果のコレクションを生成する場合、中間のList
をスキップして結果を直接Map
に収集することはできませんか?明らかに、はい、そうです。ただし、そうするためには、適切なCanBuildFrom
をmap
に渡す必要があります。それがまさにbreakOut
が行うことです。
それでは、breakOut
の定義を見てみましょう。
def breakOut[From, T, To](implicit b : CanBuildFrom[Nothing, T, To]) =
new CanBuildFrom[From, T, To] {
def apply(from: From) = b.apply() ; def apply() = b.apply()
}
breakOut
はパラメーター化されており、CanBuildFrom
のインスタンスを返すことに注意してください。たまたま、From
がCanBuildFrom[List[String], (Int, String), Map[Int, String]]
を予期していることがわかっているため、タイプT
、To
、およびmap
はすでに推論されています。したがって:
From = List[String]
T = (Int, String)
To = Map[Int, String]
最後に、breakOut
自体が受け取った暗黙の内容を調べてみましょう。タイプはCanBuildFrom[Nothing,T,To]
です。これらの型はすべてわかっているため、CanBuildFrom[Nothing,(Int,String),Map[Int,String]]
型の暗黙的な型が必要であると判断できます。しかし、そのような定義はありますか?
CanBuildFrom
の定義を見てみましょう。
trait CanBuildFrom[-From, -Elem, +To]
extends AnyRef
したがって、CanBuildFrom
は、その最初の型パラメーターが反変です。 Nothing
は最下位クラス(つまり、すべてのサブクラス)であるため、anyクラスをNothing
の代わりに使用できることを意味します。
このようなビルダーが存在するため、Scalaはそれを使用して目的の出力を生成できます。
ビルダーについて
Scalaのコレクションライブラリの多くのメソッドは、元のコレクションを取得し、それを何らかの方法で処理し(map
の場合、各要素を変換し)、結果を新しいコレクションに格納します。
コードの再利用を最大化するために、この結果の保存はbuilder(scala.collection.mutable.Builder
)を介して行われます。これは基本的に要素の追加と結果のコレクションを返します。この結果のコレクションのタイプは、ビルダーのタイプによって異なります。したがって、List
ビルダーはList
を返し、Map
ビルダーはMap
を返します。 map
メソッドの実装は、結果の型を考慮する必要はありません。ビルダーが処理します。
一方、それはmap
が何らかの方法でこのビルダーを受け取る必要があることを意味します。 Scala 2.8コレクションを設計する際に直面した問題は、可能な限り最高のビルダーを選択する方法でした。たとえば、Map('a' -> 1).map(_.swap)
と書くとしたら、Map(1 -> 'a')
back。一方、Map('a' -> 1).map(_._1)
はMap
を返すことができません(Iterable
を返します)。
式の既知のタイプから可能な限り最良のBuilder
を生成する魔法は、このCanBuildFrom
暗黙的によって実行されます。
CanBuildFrom
について
何が起こっているかをよりよく説明するために、マップされるコレクションがMap
ではなくList
である例を示します。後でList
に戻ります。今のところ、次の2つの式を検討してください。
Map(1 -> "one", 2 -> "two") map Function.tupled(_ -> _.length)
Map(1 -> "one", 2 -> "two") map (_._2)
最初はMap
を返し、2番目はIterable
を返します。適切なコレクションを返す魔法は、CanBuildFrom
の働きです。 map
の定義をもう一度考えて、理解してみましょう。
メソッドmap
は、TraversableLike
から継承されます。 B
およびThat
でパラメーター化され、クラスをパラメーター化する型パラメーターA
およびRepr
を使用します。両方の定義を一緒に見てみましょう。
クラスTraversableLike
は次のように定義されます:
trait TraversableLike[+A, +Repr]
extends HasNewBuilder[A, Repr] with AnyRef
def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That
A
とRepr
がどこから来たかを理解するために、Map
自体の定義を考えてみましょう。
trait Map[A, +B]
extends Iterable[(A, B)] with Map[A, B] with MapLike[A, B, Map[A, B]]
TraversableLike
はMap
を拡張するすべての特性に継承されるため、A
およびRepr
はそれらのいずれからも継承できます。ただし、最後のものが優先されます。したがって、不変のMap
と、それをTraversableLike
に接続するすべての特性の定義に従って、次のようになります。
trait Map[A, +B]
extends Iterable[(A, B)] with Map[A, B] with MapLike[A, B, Map[A, B]]
trait MapLike[A, +B, +This <: MapLike[A, B, This] with Map[A, B]]
extends MapLike[A, B, This]
trait MapLike[A, +B, +This <: MapLike[A, B, This] with Map[A, B]]
extends PartialFunction[A, B] with IterableLike[(A, B), This] with Subtractable[A, This]
trait IterableLike[+A, +Repr]
extends Equals with TraversableLike[A, Repr]
trait TraversableLike[+A, +Repr]
extends HasNewBuilder[A, Repr] with AnyRef
Map[Int, String]
の型パラメーターをチェーン全体に渡して渡すと、TraversableLike
に渡される型は、map
によって使用されることがわかります。
A = (Int,String)
Repr = Map[Int, String]
例に戻ると、最初のマップは((Int, String)) => (Int, Int)
型の関数を受け取り、2番目のマップは((Int, String)) => String
型の関数を受け取っています。二重括弧を使用して、受け取ったタプルであることを強調しています。これは、先ほど見たA
のタイプだからです。
その情報を使用して、他のタイプを考えてみましょう。
map Function.tupled(_ -> _.length):
B = (Int, Int)
map (_._2):
B = String
最初のmap
が返す型はMap[Int,Int]
であり、2番目はIterable[String]
であることがわかります。 map
の定義を見ると、これらがThat
の値であることが簡単にわかります。しかし、彼らはどこから来たのでしょうか?
関連するクラスのコンパニオンオブジェクトの内部を見ると、それらを提供するいくつかの暗黙の宣言があります。オブジェクトMap
について:
implicit def canBuildFrom [A, B] : CanBuildFrom[Map, (A, B), Map[A, B]]
クラスがIterable
によって拡張されているオブジェクトMap
について:
implicit def canBuildFrom [A] : CanBuildFrom[Iterable, A, Iterable[A]]
これらの定義は、パラメーター化されたCanBuildFrom
のファクトリーを提供します。
Scalaは、利用可能な最も具体的な暗黙的なものを選択します。最初のケースでは、最初のCanBuildFrom
でした。 2番目のケースでは、最初のケースが一致しなかったため、2番目のCanBuildFrom
を選択しました。
質問に戻る
質問のコード、List
とmap
の定義(もう一度)を見て、型がどのように推論されるかを見てみましょう。
val map : Map[Int,String] = List("London", "Paris").map(x => (x.length, x))(breakOut)
sealed abstract class List[+A]
extends LinearSeq[A] with Product with GenericTraversableTemplate[A, List] with LinearSeqLike[A, List[A]]
trait LinearSeqLike[+A, +Repr <: LinearSeqLike[A, Repr]]
extends SeqLike[A, Repr]
trait SeqLike[+A, +Repr]
extends IterableLike[A, Repr]
trait IterableLike[+A, +Repr]
extends Equals with TraversableLike[A, Repr]
trait TraversableLike[+A, +Repr]
extends HasNewBuilder[A, Repr] with AnyRef
def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That
List("London", "Paris")
のタイプはList[String]
であるため、A
で定義されているタイプRepr
およびTraversableLike
は次のとおりです。
A = String
Repr = List[String]
(x => (x.length, x))
の型は(String) => (Int, String)
であるため、B
の型は次のとおりです。
B = (Int, String)
最後の未知のタイプThat
は、map
の結果のタイプであり、すでにあります:
val map : Map[Int,String] =
そう、
That = Map[Int, String]
つまり、breakOut
は、必ずCanBuildFrom[List[String], (Int, String), Map[Int, String]]
のタイプまたはサブタイプを返す必要があります。
ダニエルの答えを基にしたいと思います。それは非常に徹底的でしたが、コメントで述べたように、ブレイクアウトが何をするのかを説明していません。
Re:明示的なビルダーのサポート(2009-10-23)から取ったものです。
コンパイラーに、どのBuilderを暗黙的に選択するかについての提案を提供します(本質的に、コンパイラーは、状況に最適であると考えるファクトリーを選択できます)。
たとえば、次を参照してください。
scala> import scala.collection.generic._
import scala.collection.generic._
scala> import scala.collection._
import scala.collection._
scala> import scala.collection.mutable._
import scala.collection.mutable._
scala>
scala> def breakOut[From, T, To](implicit b : CanBuildFrom[Nothing, T, To]) =
| new CanBuildFrom[From, T, To] {
| def apply(from: From) = b.apply() ; def apply() = b.apply()
| }
breakOut: [From, T, To]
| (implicit b: scala.collection.generic.CanBuildFrom[Nothing,T,To])
| Java.lang.Object with
| scala.collection.generic.CanBuildFrom[From,T,To]
scala> val l = List(1, 2, 3)
l: List[Int] = List(1, 2, 3)
scala> val imp = l.map(_ + 1)(breakOut)
imp: scala.collection.immutable.IndexedSeq[Int] = Vector(2, 3, 4)
scala> val arr: Array[Int] = l.map(_ + 1)(breakOut)
imp: Array[Int] = Array(2, 3, 4)
scala> val stream: Stream[Int] = l.map(_ + 1)(breakOut)
stream: Stream[Int] = Stream(2, ?)
scala> val seq: Seq[Int] = l.map(_ + 1)(breakOut)
seq: scala.collection.mutable.Seq[Int] = ArrayBuffer(2, 3, 4)
scala> val set: Set[Int] = l.map(_ + 1)(breakOut)
seq: scala.collection.mutable.Set[Int] = Set(2, 4, 3)
scala> val hashSet: HashSet[Int] = l.map(_ + 1)(breakOut)
seq: scala.collection.mutable.HashSet[Int] = Set(2, 4, 3)
期待される型に最適に一致するように、コンパイラによって暗黙的に戻り値の型が選択されていることがわかります。受信変数の宣言方法に応じて、異なる結果が得られます。
以下は、ビルダーを指定する同等の方法です。この場合、コンパイラはビルダーのタイプに基づいて予想されるタイプを推測します。
scala> def buildWith[From, T, To](b : Builder[T, To]) =
| new CanBuildFrom[From, T, To] {
| def apply(from: From) = b ; def apply() = b
| }
buildWith: [From, T, To]
| (b: scala.collection.mutable.Builder[T,To])
| Java.lang.Object with
| scala.collection.generic.CanBuildFrom[From,T,To]
scala> val a = l.map(_ + 1)(buildWith(Array.newBuilder[Int]))
a: Array[Int] = Array(2, 3, 4)
Daniel Sobralの答えは素晴らしく、 Architecture of Scala Collections (Scalaのプログラミングの第25章)と一緒に読む必要があります。
breakOut
と呼ばれる理由を詳しく説明したかっただけです。
breakOut
と呼ばれるのですか?あるタイプから別のタイプにブレークアウトしたいので:
どのタイプからどのタイプに分割しますか?例としてmap
のSeq
関数を見てみましょう:
_Seq.map[B, That](f: (A) -> B)(implicit bf: CanBuildFrom[Seq[A], B, That]): That
_
次のようなシーケンスの要素のマッピングから直接Mapを構築したい場合:
_val x: Map[String, Int] = Seq("A", "BB", "CCC").map(s => (s, s.length))
_
コンパイラは文句を言います:
_error: type mismatch;
found : Seq[(String, Int)]
required: Map[String,Int]
_
Seqは別のSeqの構築方法しか知らない(つまり、暗黙の_CanBuildFrom[Seq[_], B, Seq[B]]
_ビルダーファクトリが利用可能ですが、[〜#〜] no [〜#〜]があるためです。 SeqからMapへのビルダーファクトリ)。
コンパイルするためには、何らかの方法でbreakOut
のタイプの要件、また、使用するmap
関数のマップを生成するビルダーを構築できます。
ダニエルが説明したように、breakOutには次のシグネチャがあります。
_def breakOut[From, T, To](implicit b: CanBuildFrom[Nothing, T, To]): CanBuildFrom[From, T, To] =
// can't just return b because the argument to apply could be cast to From in b
new CanBuildFrom[From, T, To] {
def apply(from: From) = b.apply()
def apply() = b.apply()
}
_
Nothing
はすべてのクラスのサブクラスであるため、どのビルダーファクトリも_implicit b: CanBuildFrom[Nothing, T, To]
_の代わりに使用できます。暗黙的なパラメーターを提供するためにbreakOut関数を使用した場合:
_val x: Map[String, Int] = Seq("A", "BB", "CCC").map(s => (s, s.length))(collection.breakOut)
_
breakOut
は必要なタイプのCanBuildFrom[Seq[(String, Int)], (String, Int), Map[String, Int]]
を提供できるため、コンパイラはタイプCanBuildFrom[Map[_, _], (A, B), Map[A, B]]
の暗黙のビルダーファクトリを見つけることができるため、コンパイルされます。 _CanBuildFrom[Nothing, T, To]
_の、実際のビルダーの作成に使用するbreakOut用。
CanBuildFrom[Map[_, _], (A, B), Map[A, B]]
はMapで定義され、基礎となるMapを使用するMapBuilder
を単純に開始することに注意してください。
これで問題が解決することを願っています。
breakOut
の機能を理解するための簡単な例:
scala> import collection.breakOut
import collection.breakOut
scala> val set = Set(1, 2, 3, 4)
set: scala.collection.immutable.Set[Int] = Set(1, 2, 3, 4)
scala> set.map(_ % 2)
res0: scala.collection.immutable.Set[Int] = Set(1, 0)
scala> val seq:Seq[Int] = set.map(_ % 2)(breakOut)
seq: Seq[Int] = Vector(1, 0, 1, 0) // map created a Seq[Int] instead of the default Set[Int]