Di Scala 2.8 , ada objek di 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()
}
Saya telah diberitahu bahwa ini menghasilkan:
> 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)
Apa yang terjadi disini? Mengapa breakOut
dipanggil sebagai argumen untuk saya List
?
scala
scala-2.8
scala-collections
oxbow_lakes
sumber
sumber
List
, tetapi untukmap
.Jawaban:
Jawabannya ditemukan pada definisi
map
: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 yangMap
keluar. Cara paling jelas untuk melakukan itu akan menghasilkanList[(Int, String)]
koleksi perantara , dan kemudian mengubahnya.Mengingat bahwa
map
menggunakan aBuilder
untuk menghasilkan koleksi yang dihasilkan, tidak akan mungkin untuk melewatkan perantaraList
dan mengumpulkan hasilnya langsung menjadiMap
? Jelas, ya, benar. Namun, untuk melakukannya, kita perlu memberikan hakCanBuildFrom
kepadamap
, dan memang itulah yangbreakOut
dilakukannya.Mari kita lihat definisi
breakOut
:Perhatikan bahwa
breakOut
parameternya, dan itu mengembalikan instance dariCanBuildFrom
. Seperti yang terjadi, jenisnyaFrom
,T
danTo
sudah disimpulkan, karena kita tahu itumap
yang diharapkanCanBuildFrom[List[String], (Int, String), Map[Int, String]]
. Karena itu:Untuk menyimpulkan, mari kita periksa implisit yang diterima dengan
breakOut
sendirinya. Itu adalah tipeCanBuildFrom[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:Begitu
CanBuildFrom
juga kontra-varian pada parameter tipe pertama. KarenaNothing
kelas bawah (yaitu, itu adalah subkelas dari segalanya), itu berarti setiap kelas dapat digunakan sebagai penggantiNothing
.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 aList
,Map
pembangun akan mengembalikan aMap
, dan seterusnya. Implementasimap
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 menulisMap('a' -> 1).map(_.swap)
, saya ingin mendapatkanMap(1 -> 'a')
kembali. Di sisi lain, aMap('a' -> 1).map(_._1)
tidak dapat mengembalikanMap
(mengembalikanIterable
).Keajaiban menghasilkan yang terbaik
Builder
dari jenis ekspresi yang dikenal dilakukan melaluiCanBuildFrom
implisit ini .Tentang
CanBuildFrom
Untuk lebih menjelaskan apa yang terjadi, saya akan memberikan contoh di mana koleksi yang dipetakan adalah
Map
aList
. Saya akan kembali lagiList
nanti. Untuk saat ini, pertimbangkan dua ungkapan ini:Yang pertama mengembalikan
Map
dan yang kedua mengembalikanIterable
. Keajaiban mengembalikan koleksi yang pas adalah karyaCanBuildFrom
. Mari kita pertimbangkan definisimap
lagi untuk memahaminya.Metode
map
ini diwarisi dariTraversableLike
. Ini diparameterisasi padaB
danThat
, dan menggunakan tipe parameterA
danRepr
, yang parameterkan kelas. Mari kita lihat kedua definisi bersama:Kelas
TraversableLike
didefinisikan sebagai:Untuk memahami dari mana
A
danRepr
berasal, mari pertimbangkan definisiMap
itu sendiri:Karena
TraversableLike
diwariskan oleh semua sifat yang meluasMap
,A
danRepr
bisa diwarisi dari mereka. Yang terakhir mendapatkan preferensi. Jadi, mengikuti definisi kekekalanMap
dan semua sifat yang menghubungkannyaTraversableLike
, kita memiliki:Jika Anda melewatkan parameter tipe dari
Map[Int, String]
semua jalan ke bawah rantai, kami menemukan bahwa tipe yang diteruskan keTraversableLike
, dan, dengan demikian, digunakan olehmap
, adalah: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 yangA
kita lihat.Dengan informasi itu, mari pertimbangkan jenis-jenis lainnya.
Kita dapat melihat bahwa tipe yang dikembalikan oleh yang pertama
map
adalahMap[Int,Int]
, dan yang kedua adalahIterable[String]
. Melihatmap
definisi itu, mudah untuk melihat bahwa ini adalah nilai dariThat
. Tapi dari mana asalnya?Jika kita melihat ke dalam objek pendamping dari kelas yang terlibat, kita melihat beberapa deklarasi implisit menyediakannya. Pada objek
Map
:Dan pada objek
Iterable
, yang kelasnya diperpanjang olehMap
: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 keduaCanBuildFrom
.Kembali ke Pertanyaan
Mari kita lihat kode untuk pertanyaan,
List
's danmap
' s definisi (lagi) untuk melihat bagaimana jenis tersebut disimpulkan:Jenis
List("London", "Paris")
isList[String]
, jadi jenisA
danRepr
didefinisikanTraversableLike
adalah:Jenisnya
(x => (x.length, x))
adalah(String) => (Int, String)
, jadi jenisnyaB
adalah:Jenis yang terakhir tidak diketahui,
That
adalah jenis hasilmap
, dan kita sudah memiliki itu juga:Begitu,
Itu berarti
breakOut
harus, tentu saja, mengembalikan jenis atau subtipe dariCanBuildFrom[List[String], (Int, String), Map[Int, String]]
.sumber
Saya ingin membangun berdasarkan jawaban Daniel. Itu sangat menyeluruh, tetapi seperti disebutkan dalam komentar, itu tidak menjelaskan apa yang dilakukan breakout.
Diambil dari Re: Dukungan untuk Pembangun eksplisit (2009-10-23), berikut adalah apa yang saya yakini tidak terjadi:
Ini memberikan kompiler saran yang Builder untuk memilih secara implisit (pada dasarnya itu memungkinkan kompiler untuk memilih pabrik yang menurutnya paling cocok dengan situasi.)
Misalnya, lihat yang berikut:
Anda dapat melihat tipe pengembalian secara implisit dipilih oleh kompiler untuk paling cocok dengan jenis yang diharapkan. Bergantung pada bagaimana Anda mendeklarasikan variabel penerima, Anda mendapatkan hasil yang berbeda.
Berikut ini akan menjadi cara yang setara untuk menentukan pembangun. Catatan dalam hal ini, kompiler akan menyimpulkan tipe yang diharapkan berdasarkan tipe pembangun:
sumber
breakOut
"? Saya berpikir sesuatu seperticonvert
ataubuildADifferentTypeOfCollection
(tetapi lebih pendek) mungkin lebih mudah diingat.Jawaban Daniel Sobral sangat bagus, dan harus dibaca bersama dengan Arsitektur Koleksi Scala (Bab 25 tentang Pemrograman dalam Scala).
Saya hanya ingin menjelaskan mengapa ini disebut
breakOut
:Mengapa disebut demikian
breakOut
?Karena kami ingin keluar dari satu jenis ke jenis lainnya :
Keluar dari tipe apa ke tipe apa? Mari kita lihat
map
fungsi padaSeq
sebagai contoh:Jika kami ingin membangun Peta langsung dari pemetaan elemen-elemen urutan seperti:
Kompiler akan mengeluh:
Alasannya adalah bahwa Seq hanya tahu cara membangun Seq lain (yaitu ada
CanBuildFrom[Seq[_], B, Seq[B]]
pabrik pembangun implisit yang tersedia, tetapi tidak ada pabrik pembangun dari Seq ke Peta).Untuk mengkompilasi, kita perlu entah bagaimana
breakOut
persyaratan jenis , dan dapat membangun pembangun yang menghasilkan Peta untukmap
fungsi yang akan digunakan.Seperti yang dijelaskan Daniel, breakOut memiliki tanda tangan berikut:
Nothing
adalah subkelas dari semua kelas, sehingga setiap pabrik pembangun dapat diganti di tempatimplicit b: CanBuildFrom[Nothing, T, To]
. Jika kami menggunakan fungsi breakOut untuk memberikan parameter implisit:Itu akan dikompilasi, karena
breakOut
mampu memberikan jenis yang diperlukanCanBuildFrom[Seq[(String, Int)], (String, Int), Map[String, Int]]
, sedangkan kompiler dapat menemukan pabrik pembangun implisit dari jenisCanBuildFrom[Map[_, _], (A, B), Map[A, B]]
, di tempatCanBuildFrom[Nothing, T, To]
, untuk breakOut digunakan untuk membuat pembangun yang sebenarnya.Catatan yang
CanBuildFrom[Map[_, _], (A, B), Map[A, B]]
didefinisikan dalam Peta, dan hanya memulaiMapBuilder
yang menggunakan Peta yang mendasarinya.Semoga ini jelas.
sumber
Contoh sederhana untuk memahami apa yang
breakOut
dilakukan:sumber
val seq:Seq[Int] = set.map(_ % 2).toVector
tidak akan memberi Anda nilai yang diulang karenaSet
telah disimpan untukmap
.set.map(_ % 2)
menciptakan yangSet(1, 0)
pertama, yang kemudian akan dikonversi menjadiVector(1, 0)
.