229

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)

ここで何が起こっているのですか?my への引数としてbreakOut呼び出されるのはなぜですか?List

4

4 に答える 4

335

答えは の定義にありmapます:

def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That 

2 つのパラメーターがあることに注意してください。1 つ目は関数で、2 つ目は暗黙的です。その暗黙的な指定がない場合、Scala は利用可能な最も具体的なものを選択します。

breakOut

それで、の目的はbreakOut何ですか?質問の例を考えてみましょう。文字列のリストを取得し、各文字列を tuple に変換し(Int, String)、そこから を生成Mapします。これを行う最も明白な方法は、中間List[(Int, String)]コレクションを作成してから変換することです。

mapを使用して結果のコレクションを生成することを考えるとBuilder、仲介をスキップしListて結果を直接収集することは可能ではないでしょうMapか? 明らかに、そうです。ただし、そうするには、適切なを に渡す必要がありCanBuildFrommapそれはまさにそれです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およびTToすでに推測されてmapCanBuildFrom[List[String], (Int, String), Map[Int, String]]ます。したがって:

From = List[String]
T = (Int, String)
To = Map[Int, String]

breakOut結論として、それ自体で受け取られた暗黙的なものを調べてみましょう。タイプCanBuildFrom[Nothing,T,To]です。これらの型はすべて知っているので、暗黙の type が必要であると判断できCanBuildFrom[Nothing,(Int,String),Map[Int,String]]ます。しかし、そのような定義はありますか?

CanBuildFromの定義を見てみましょう。

trait CanBuildFrom[-From, -Elem, +To] 
extends AnyRef

CanBuildFromそのため、最初の型パラメーターの反変です。Nothingは下位クラス (つまり、すべてのサブクラス) であるため、任意のクラスを の代わりに使用できることを意味ますNothing

このようなビルダーが存在するため、Scala はそれを使用して目的の出力を生成できます。

ビルダーについて

Scala のコレクション ライブラリの多くのメソッドは、元のコレクションを取得し、何らかの方法で処理し (の場合はmap各要素を変換)、結果を新しいコレクションに格納することで構成されます。

コードの再利用を最大化するために、この結果の保存はビルダー( scala.collection.mutable.Builder) を介して行われます。ビルダーは基本的に、要素の追加と結果のコレクションの返しの 2 つの操作をサポートします。この結果のコレクションのタイプは、ビルダーのタイプによって異なります。したがって、Listビルダーは を返し、ListビルダーMapは を返しMapます。メソッドの実装はmap、結果の型に関係する必要はありません。ビルダーが処理します。

一方で、それはmapどういうわけかこのビルダーを受け取る必要があることを意味します。Scala 2.8 コレクションを設計する際に直面した問題は、可能な限り最良のビルダーを選択する方法でした。たとえば、私が書くとしたら、バックMap('a' -> 1).map(_.swap)を取得したいと思います。Map(1 -> 'a')一方、 aは aMap('a' -> 1).map(_._1)を返すことはできませんMap(それは an を返しますIterable)。

Builder既知の型の式から可能な限り最高のものを生成するという魔法は、このCanBuildFrom暗黙的な方法で実行されます。

CanBuildFrom

何が起こっているのかをよりよく説明するために、マップされているコレクションがMapではなく である例を挙げますList。後で戻りますList。とりあえず、次の 2 つの式を考えてみましょう。

Map(1 -> "one", 2 -> "two") map Function.tupled(_ -> _.length)
Map(1 -> "one", 2 -> "two") map (_._2)

1 つ目は aMapを返し、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ただし、最後のものが優先されます。したがって、 immutable の定義と、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]

例に戻ると、最初の map は type の関数を((Int, String)) => (Int, Int)受け取り、2 番目の map は type の関数を受け取ります((Int, String)) => String。二重かっこを使用して、受け取ったタプルであることを強調していAます。

その情報を使用して、他のタイプを考えてみましょう。

map Function.tupled(_ -> _.length):
B = (Int, Int)

map (_._2):
B = String

最初に返される型mapMap[Int,Int]で、2 番目はIterable[String]です。の定義を見るmapと、これらが の値であることが容易にわかりますThat。しかし、彼らはどこから来たのですか?

関連するクラスのコンパニオン オブジェクトの内部を見ると、それらを提供するいくつかの暗黙の宣言が表示されます。オブジェクト上Map:

implicit def  canBuildFrom [A, B] : CanBuildFrom[Map, (A, B), Map[A, B]]  

そしてIterable、クラスがによって拡張された object 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]であるため、 on と の型は次のようARepr定義され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]]

于 2009-11-11T16:53:02.847 に答える
86

ダニエルの答えに基づいて構築したいと思います。それは非常に徹底的でしたが、コメントに記載されているように、ブレイクアウトが何をするかを説明していません.

Re: Support for explicit Builders (2009-10-23)からの引用ですが、ブレイクアウトが行うと私が信じていることは次のとおりです。

どのビルダーを暗黙的に選択するかについてコンパイラーに提案します (基本的に、コンパイラーは状況に最も適していると考えるファクトリを選択できます)。

たとえば、次を参照してください。

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)
于 2011-09-13T15:35:54.103 に答える
6

何が行われるかを理解するための簡単な例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]
于 2016-10-17T21:09:06.330 に答える