scala traversable collection
Scala 2.8 breakOut (3)
Daniel Sobralの答えは素晴らしく、 Scala CollectionsのArchitecture(ScalaのProgrammingの第25章)と一緒に読まれるべきです。
私はちょうどそれがbreakOut
と呼ばれる理由を詳しく説明したいとbreakOut
ます:
breakOut
と呼ばれるのはなぜですか?
私たちはあるタイプから別のタイプへと脱出したいからです。
どんなタイプからどんなタイプに飛び出しますか? 例としてSeq
のmap
関数を見てみましょう:
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]]
Builderファクトリが利用可能ですが、SeqからMapへのBuilderファクトリはありません。
コンパイルするには、型の要件のbreakOut
を必要とし、 map
関数が使用するMapを生成するBuilderを構築できるようにする必要があります。
Danielが説明したように、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
ので、任意のBuilderファクトリをimplicit b: CanBuildFrom[Nothing, T, To]
代わりに使用できます。 breakOut関数を使用して暗黙のパラメーターを指定した場合:
val x: Map[String, Int] = Seq("A", "BB", "CCC").map(s => (s, s.length))(collection.breakOut)
コンパイラは暗黙的なビルダーファクトリを見つけることができるが、 CanBuildFrom[Seq[(String, Int)], (String, Int), Map[String, Int]]
はCanBuildFrom[Seq[(String, Int)], (String, Int), Map[String, Int]]
の必要な型を提供できるのでコンパイルする。 CanBuildFrom[Nothing, T, To]
代わりにCanBuildFrom[Map[_, _], (A, B), Map[A, B]]
を使用して、実際のビルダーの作成に使用します。
CanBuildFrom[Map[_, _], (A, B), Map[A, B]]
はMapで定義され、基礎となるMapを使用するMapBuilder
を開始するだけであることにMapBuilder
してください。
これは物事をクリアすることを願っています。
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)
ここで何が起こっているのですか? なぜ私のList
引数として breakOut
が呼び出されていますか?
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]
答えはmap
の定義にありmap
:
def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That
2つのパラメータがあることに注意してください。 最初はあなたの関数であり、第2は暗黙的です。 暗黙的に指定しないと、Scalaは利用可能な最も具体的なものを選択します。
breakOut
について
では、 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
、 T
、 To
型は、 map
がCanBuildFrom[List[String], (Int, String), Map[Int, String]]
予期していることを既に知っているので、既に推測されています。 したがって:
From = List[String]
T = (Int, String)
To = Map[Int, String]
結論として、 breakOut
自体が受け取った暗黙的な内容を調べてみましょう。 CanBuildFrom[Nothing,T,To]
のタイプCanBuildFrom[Nothing,T,To]
。 私たちはすでにこれらのすべての型を知っているので、型CanBuildFrom[Nothing,(Int,String),Map[Int,String]]
暗黙の型が必要であると判断できます。 しかし、そのような定義はありますか?
CanBuildFrom
の定義を見てみましょう:
trait CanBuildFrom[-From, -Elem, +To]
extends AnyRef
したがって、 CanBuildFrom
は、最初の型パラメータのCanBuildFrom
型です。 Nothing
はボトムクラス(つまり、すべてのサブクラス)であるため、 Nothing
代わりにどのクラスも使用できます。
このようなビルダーが存在するので、Scalaはそれを使用して目的の出力を生成できます。
ビルダーについて
Scalaのコレクションライブラリの多くのメソッドは、元のコレクションを取り出し、何らかの形で処理し( map
の場合は各要素を変換する)、結果を新しいコレクションに格納することから成ります。
コードの再利用を最大限にするために、この結果の格納はビルダ ( scala.collection.mutable.Builder
)によって行われます。これは基本的に、要素の追加と結果のコレクションの2つの操作をサポートしています。 この結果として得られるコレクションのタイプは、ビルダーのタイプによって異なります。 したがって、 List
BuilderはList
を返し、 Map
BuilderはMap
を返します。 map
メソッドの実装は、結果の型に関わる必要はありません。ビルダーがそれを処理します。
一方、 map
はこのビルダーを何とか受け取る必要があることを意味しmap
。 Scala 2.8 Collectionsを設計する際に直面する問題は、可能な限り最良のビルダーを選択する方法でした。 たとえば、 Map('a' -> 1).map(_.swap)
と書くと、 Map(1 -> 'a')
返したいと思います。 一方、 Map('a' -> 1).map(_._1)
はMap
(それはIterable
返します)を返すことはできません。
既知の型の式から最良のBuilder
を生成するという魔法は、このCanBuildFrom
暗黙的に実行されます。
CanBuildFrom
について
何が起こっているのかを詳しく説明するために、マップされているコレクションがList
ではなくマップである例を挙げます。 後でList
に戻ってきます。 今のところ、次の2つの表現を考えてみましょう。
Map(1 -> "one", 2 -> "two") map Function.tupled(_ -> _.length)
Map(1 -> "one", 2 -> "two") map (_._2)
最初のMap
はMap
を返し、2番目のMap
は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
まで渡すと、 TraversableLike
に渡される型がmap
によって使用されることがわかります:
A = (Int,String)
Repr = Map[Int, String]
この例に戻って、最初のマップは((Int, String)) => (Int, Int)
の型の関数を受け取り、2番目のマップは型((Int, String)) => String
関数を受け取ります。 私は二重括弧を使用して、受信したタプルであることを強調します。これは、 A
のタイプとA
です。
その情報を使って、他のタイプを考えてみましょう。
map Function.tupled(_ -> _.length):
B = (Int, Int)
map (_._2):
B = String
最初のmap
返される型がMap[Int,Int]
で、2番目がIterable[String]
がIterable[String]
ます。 map
の定義を見ると、これらがその値であることは容易にわかります。 しかし、彼らはどこから来たのですか?
関係するクラスのコンパニオンオブジェクトを調べると、それらを提供する暗黙の宣言があります。 オブジェクト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
選択し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]
なので、 TraversableLike
定義されたタイプA
とRepr
は次のとおりです。
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]]
型またはサブタイプを返す必要があります。