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 Map
keluar. Cara paling jelas untuk melakukan itu akan menghasilkan List[(Int, String)]
koleksi perantara , dan kemudian mengubahnya.
Mengingat bahwa map
menggunakan a Builder
untuk menghasilkan koleksi yang dihasilkan, tidak akan mungkin untuk melewatkan perantara List
dan mengumpulkan hasilnya langsung menjadi Map
? Jelas, ya, benar. Namun, untuk melakukannya, kita perlu memberikan hak CanBuildFrom
kepada map
, dan memang itulah yang breakOut
dilakukannya.
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 breakOut
parameternya, dan itu mengembalikan instance dari CanBuildFrom
. Seperti yang terjadi, jenisnya From
, T
dan To
sudah disimpulkan, karena kita tahu itu map
yang 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 breakOut
sendirinya. 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 CanBuildFrom
definisi:
trait CanBuildFrom[-From, -Elem, +To]
extends AnyRef
Begitu CanBuildFrom
juga kontra-varian pada parameter tipe pertama. Karena Nothing
kelas 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, List
pembangun akan mengembalikan a List
, Map
pembangun akan mengembalikan a Map
, dan seterusnya. Implementasi map
metode tidak perlu memusatkan perhatian pada jenis hasilnya: pembangun akan mengurusnya.
Di sisi lain, itu berarti bahwa map
perlu 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 Builder
dari jenis ekspresi yang dikenal dilakukan melalui CanBuildFrom
implisit ini .
Tentang CanBuildFrom
Untuk lebih menjelaskan apa yang terjadi, saya akan memberikan contoh di mana koleksi yang dipetakan adalah Map
a List
. Saya akan kembali lagi List
nanti. 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 Map
dan yang kedua mengembalikan Iterable
. Keajaiban mengembalikan koleksi yang pas adalah karya CanBuildFrom
. Mari kita pertimbangkan definisi map
lagi untuk memahaminya.
Metode map
ini diwarisi dari TraversableLike
. Ini diparameterisasi pada B
dan That
, dan menggunakan tipe parameter A
dan Repr
, yang parameterkan kelas. Mari kita lihat kedua definisi bersama:
Kelas TraversableLike
didefinisikan 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 A
dan Repr
berasal, mari pertimbangkan definisi Map
itu sendiri:
trait Map[A, +B]
extends Iterable[(A, B)] with Map[A, B] with MapLike[A, B, Map[A, B]]
Karena TraversableLike
diwariskan oleh semua sifat yang meluas Map
, A
dan Repr
bisa diwarisi dari mereka. Yang terakhir mendapatkan preferensi. Jadi, mengikuti definisi kekekalan Map
dan 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 A
kita 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 map
adalah Map[Int,Int]
, dan yang kedua adalah Iterable[String]
. Melihat map
definisi 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 A
dan Repr
didefinisikan TraversableLike
adalah:
A = String
Repr = List[String]
Jenisnya (x => (x.length, x))
adalah (String) => (Int, String)
, jadi jenisnya B
adalah:
B = (Int, String)
Jenis yang terakhir tidak diketahui, That
adalah jenis hasil map
, dan kita sudah memiliki itu juga:
val map : Map[Int,String] =
Begitu,
That = Map[Int, String]
Itu berarti breakOut
harus, tentu saja, mengembalikan jenis atau subtipe dari CanBuildFrom[List[String], (Int, String), Map[Int, String]]
.
List
, tetapi untukmap
.