1. ホーム
  2. スカラ

[解決済み】Scala 2.8 breakOut

2022-04-06 23:36:12

質問

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つのパラメータを持っていることに注意してください。ひとつはあなたの関数で、もうひとつは暗黙的なものです。もし暗黙の了解を与えなかった場合、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 . たまたま、型 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] . これらの型はすでにすべて知っているので、以下の型の暗黙的なものが必要だと判断できます。 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 , a 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

何が起こっているかをよりよく説明するために、マッピングされるコレクションが 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 . このパラメータは 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 

を理解するために ARepr の定義について考えてみましょう。 Map そのものである。

trait Map[A, +B] 
extends Iterable[(A, B)] with Map[A, B] with MapLike[A, B, Map[A, B]]

なぜなら TraversableLike を拡張するすべてのトレイトに継承されます。 Map , ARepr は、そのいずれからも継承される可能性があります。しかし、最後のものが優先されます。そこで、イミュータブルの定義にしたがって 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

によって返される型は、最初の 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つ目のケースでは、1つ目がマッチしなかったので、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]] .