Scala 2.8ブレークアウト


225

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ですか?


13
些細な答えは、それはへの引数ではありませんListが、それにmap
ダニエルC.ソブラル

回答:


325

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

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

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

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。偶然にも、種類はFromTTo私たちはそれを知っているので、すでに、推論されている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,(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戻りますビルダーはListMapビルダーが戻りますMapように、と。mapメソッドの実装は、結果のタイプに関係する必要はありません。ビルダーがそれを処理します。

一方、それはmapどういうわけかこのビルダーを受け取る必要があることを意味します。Scala 2.8コレクションを設計するときに直面した問題は、可能な限り最高のビルダーを選択する方法でした。たとえば、と書いた場合Map('a' -> 1).map(_.swap)Map(1 -> 'a')取り戻したいと思います。一方、a Map('a' -> 1).map(_._1)はa を返すことができません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)

1つ目はa Mapを返し、2つ目はを返しますIterable。フィッティングコレクションを返す魔法はの仕事ですCanBuildFrommapそれを理解するために再びの定義を考えてみましょう。

メソッドmapはから継承されTraversableLikeます。これは、上でパラメータ化されたBThatし、型パラメータを使用するARepr、クラスをパラメータ化、。両方の定義を一緒に見てみましょう:

クラス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拡張するすべての特性によって継承されMapAそして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]

例に戻ると、最初のマップはtypeの関数を受け取り((Int, String)) => (Int, Int)、2番目のマップは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、そのクラスが以下によって拡張されるオブジェクトについて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]ので、種類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]]ます。


61
ダニエル、あなたの答えのタイプを掘り下げることができますが、私が最後に到達すると、私はどんな高度な理解も得ていないように感じます。ブレークアウトと何ですか?「breakOut」の名前はどこから来たのですか(私は何から脱出していますか)?この場合、マップを取得するためになぜ必要なのですか?確かに存在しているいくつかのこれらの質問に簡単に答えへの道は?(たとえ細部を把握するために長いタイプの溝掘りが依然として必要な場合でも)
Seth Tisue 2010年

3
@Sethそれは妥当な懸念事項ですが、私がその仕事をするかどうかはわかりません。これの起源はここにあります:article.gmane.org/gmane.comp.lang.scala.internals/1812/…。考えてみますが、今のところそれを改善する方法はあまり考えられません。
ダニエルC.ソブラル

2
Map [Int、String]の結果タイプ全体を指定することを避け、代わりに次のようなものを書くことができる方法はありますか: 'val map = List( "London"、 "Paris")。map(x =>(x。 length、x))(breakOut [... Map]) '
IttayD

9
@SethTisueこの説明を読んだところ、ビルダーがList [String]からビルドする必要があるという要件を「打ち破る」には、breakOutが必要であるようです。コンパイラは、提供できないCanBuildFrom [List [String]、(Int、String)、Map [Int、String]]を必要としています。breakOut関数は、CanBuildFromの最初の型パラメーターをNothingに設定することでこれを破棄することでこれを行います。これで、CanBuildFrom [Nothing、(Int、String)、Map [Int、String]]を提供するだけで済みます。Mapクラスによって提供されるため、これは簡単です。
マーク

2
@Mark breakOutを見つけたとき、それがアドレス指定しているのを見たときの問題は、モナドが(bind / flatMapを介して)独自の型にマッピングすることを主張する方法でした。1つのモナドを使用して、別のモナドタイプにマッピングチェーンを「分解」することができます。しかし、それがAdriaan Moors(著者)がどう考えていたかはわかりません。
Ed Staub 2013年

86

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

Re:明示的なビルダーのサポート(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)

1
なぜ「breakOut」なのかしら?以下のように私は何かを考えているconvertか、buildADifferentTypeOfCollection(より短いが)覚えやすいだったかもしれません。
KajMagnus 2017年

8

Daniel Sobralの答えはすばらしいので、Scalaコレクションのアーキテクチャ(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]

配列のみが別の配列を構築する方法を知っているかどうかという理由は、(すなわち、暗黙的な存在であるCanBuildFrom[Seq[_], B, Seq[B]]ビルダー工場利用できるが、そこでNO配列から地図へのビルダー工場は)。

コンパイルするには、何とかbreakOutして型の要件を満たしmap使用する関数のマップを生成するビルダーを構築できる必要があります。

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はすべてのクラスのサブクラスなので、の代わりに任意のビルダーファクトリを使用できます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]ブレイクアウトは、実際のビルダーを作成するために使用するために、。

CanBuildFrom[Map[_, _], (A, B), Map[A, B]]はMapで定義されMapBuilderており、基になるMapを使用するを単に開始することに注意してください。

これで問題が解決することを願っています。


4

機能を理解するための簡単な例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]

例をありがとう!がのために保存されていたため、val seq:Seq[Int] = set.map(_ % 2).toVector繰り返し値も表示されません。Setmap
マシューピカリング

@MatthewPickering正解です!最初にset.map(_ % 2)作成し、Set(1, 0)次にに変換されますVector(1, 0)
fdietze
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.