Jawabannya ditemukan pada definisi map:
def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That
Perhatikan bahwa ia memiliki dua parameter. Yang pertama adalah fungsi Anda dan yang kedua adalah implisit. Jika Anda tidak memberikan yang tersirat itu, Scala akan memilih yang paling spesifik yang tersedia.
Tentang breakOut
Jadi, apa tujuannya breakOut? Pertimbangkan contoh yang diberikan untuk pertanyaan, Anda mengambil daftar string, mengubah setiap string menjadi sebuah tuple (Int, String), dan kemudian menghasilkan yang Mapkeluar. Cara paling jelas untuk melakukan itu akan menghasilkan List[(Int, String)]koleksi perantara , dan kemudian mengubahnya.
Mengingat bahwa mapmenggunakan a Builderuntuk menghasilkan koleksi yang dihasilkan, tidak akan mungkin untuk melewatkan perantara Listdan mengumpulkan hasilnya langsung menjadi Map? Jelas, ya, benar. Namun, untuk melakukannya, kita perlu memberikan hak CanBuildFromkepada map, dan memang itulah yang breakOutdilakukannya.
Mari kita lihat definisi 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()
}
Perhatikan bahwa breakOutparameternya, dan itu mengembalikan instance dari CanBuildFrom. Seperti yang terjadi, jenisnya From, Tdan Tosudah disimpulkan, karena kita tahu itu mapyang diharapkan CanBuildFrom[List[String], (Int, String), Map[Int, String]]. Karena itu:
From = List[String]
T = (Int, String)
To = Map[Int, String]
Untuk menyimpulkan, mari kita periksa implisit yang diterima dengan breakOutsendirinya. Itu adalah tipe CanBuildFrom[Nothing,T,To]. Kita sudah mengetahui semua tipe ini, sehingga kita dapat menentukan bahwa kita membutuhkan tipe implisitCanBuildFrom[Nothing,(Int,String),Map[Int,String]] . Tetapi adakah definisi seperti itu?
Mari kita lihat CanBuildFromdefinisi:
trait CanBuildFrom[-From, -Elem, +To]
extends AnyRef
Begitu CanBuildFromjuga kontra-varian pada parameter tipe pertama. Karena Nothingkelas bawah (yaitu, itu adalah subkelas dari segalanya), itu berarti setiap kelas dapat digunakan sebagai pengganti Nothing.
Karena pembangun seperti itu ada, Scala dapat menggunakannya untuk menghasilkan output yang diinginkan.
Tentang Pembangun
Banyak metode dari perpustakaan koleksi Scala terdiri dari mengambil koleksi asli, memprosesnya entah bagaimana (dalam kasus map, mentransformasikan setiap elemen), dan menyimpan hasilnya dalam koleksi baru.
Untuk memaksimalkan penggunaan kembali kode, penyimpanan hasil ini dilakukan melalui builder ( scala.collection.mutable.Builder), yang pada dasarnya mendukung dua operasi: menambahkan elemen, dan mengembalikan koleksi yang dihasilkan. Jenis koleksi yang dihasilkan ini akan tergantung pada jenis pembangun. Dengan demikian, Listpembangun akan mengembalikan a List, Mappembangun akan mengembalikan a Map, dan seterusnya. Implementasi mapmetode tidak perlu memusatkan perhatian pada jenis hasilnya: pembangun akan mengurusnya.
Di sisi lain, itu berarti bahwa mapperlu menerima pembangun ini entah bagaimana. Masalah yang dihadapi ketika merancang Koleksi Scala 2.8 adalah bagaimana memilih pembangun terbaik. Sebagai contoh, jika saya menulis Map('a' -> 1).map(_.swap), saya ingin mendapatkan Map(1 -> 'a')kembali. Di sisi lain, a Map('a' -> 1).map(_._1)tidak dapat mengembalikan Map(mengembalikan Iterable).
Keajaiban menghasilkan yang terbaik Builderdari jenis ekspresi yang dikenal dilakukan melalui CanBuildFromimplisit ini .
Tentang CanBuildFrom
Untuk lebih menjelaskan apa yang terjadi, saya akan memberikan contoh di mana koleksi yang dipetakan adalah Mapa List. Saya akan kembali lagi Listnanti. Untuk saat ini, pertimbangkan dua ungkapan ini:
Map(1 -> "one", 2 -> "two") map Function.tupled(_ -> _.length)
Map(1 -> "one", 2 -> "two") map (_._2)
Yang pertama mengembalikan Mapdan yang kedua mengembalikan Iterable. Keajaiban mengembalikan koleksi yang pas adalah karya CanBuildFrom. Mari kita pertimbangkan definisi maplagi untuk memahaminya.
Metode mapini diwarisi dari TraversableLike. Ini diparameterisasi pada Bdan That, dan menggunakan tipe parameter Adan Repr, yang parameterkan kelas. Mari kita lihat kedua definisi bersama:
Kelas TraversableLikedidefinisikan sebagai:
trait TraversableLike[+A, +Repr]
extends HasNewBuilder[A, Repr] with AnyRef
def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That
Untuk memahami dari mana Adan Reprberasal, mari pertimbangkan definisi Mapitu sendiri:
trait Map[A, +B]
extends Iterable[(A, B)] with Map[A, B] with MapLike[A, B, Map[A, B]]
Karena TraversableLikediwariskan oleh semua sifat yang meluas Map, Adan Reprbisa diwarisi dari mereka. Yang terakhir mendapatkan preferensi. Jadi, mengikuti definisi kekekalan Mapdan semua sifat yang menghubungkannya TraversableLike, kita memiliki:
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
Jika Anda melewatkan parameter tipe dari Map[Int, String]semua jalan ke bawah rantai, kami menemukan bahwa tipe yang diteruskan ke TraversableLike, dan, dengan demikian, digunakan oleh map, adalah:
A = (Int,String)
Repr = Map[Int, String]
Kembali ke contoh, peta pertama menerima fungsi tipe ((Int, String)) => (Int, Int)dan peta kedua menerima fungsi tipe ((Int, String)) => String. Saya menggunakan tanda kurung ganda untuk menekankan itu adalah tuple yang diterima, seperti itulah jenis yang Akita lihat.
Dengan informasi itu, mari pertimbangkan jenis-jenis lainnya.
map Function.tupled(_ -> _.length):
B = (Int, Int)
map (_._2):
B = String
Kita dapat melihat bahwa tipe yang dikembalikan oleh yang pertama mapadalah Map[Int,Int], dan yang kedua adalah Iterable[String]. Melihat mapdefinisi itu, mudah untuk melihat bahwa ini adalah nilai dari That. Tapi dari mana asalnya?
Jika kita melihat ke dalam objek pendamping dari kelas yang terlibat, kita melihat beberapa deklarasi implisit menyediakannya. Pada objek Map:
implicit def canBuildFrom [A, B] : CanBuildFrom[Map, (A, B), Map[A, B]]
Dan pada objek Iterable, yang kelasnya diperpanjang oleh Map:
implicit def canBuildFrom [A] : CanBuildFrom[Iterable, A, Iterable[A]]
Definisi-definisi ini menyediakan pabrik untuk diparameterisasi CanBuildFrom.
Scala akan memilih implisit paling spesifik yang tersedia. Dalam kasus pertama, itu adalah yang pertama CanBuildFrom. Dalam kasus kedua, karena yang pertama tidak cocok, ia memilih yang kedua CanBuildFrom.
Kembali ke Pertanyaan
Mari kita lihat kode untuk pertanyaan, List's dan map' s definisi (lagi) untuk melihat bagaimana jenis tersebut disimpulkan:
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
Jenis List("London", "Paris")is List[String], jadi jenis Adan Reprdidefinisikan TraversableLikeadalah:
A = String
Repr = List[String]
Jenisnya (x => (x.length, x))adalah (String) => (Int, String), jadi jenisnya Badalah:
B = (Int, String)
Jenis yang terakhir tidak diketahui, Thatadalah jenis hasil map, dan kita sudah memiliki itu juga:
val map : Map[Int,String] =
Begitu,
That = Map[Int, String]
Itu berarti breakOutharus, tentu saja, mengembalikan jenis atau subtipe dari CanBuildFrom[List[String], (Int, String), Map[Int, String]].
List, tetapi untukmap.