TL; DR langsung ke contoh terakhir
Saya akan mencoba dan merekap.
Definisi
The for
pemahaman adalah cara pintas sintaks untuk menggabungkan flatMap
dan map
dengan cara yang mudah dibaca dan alasan tentang.
Mari kita sederhanakan sedikit dan asumsikan bahwa setiap class
yang menyediakan kedua metode yang disebutkan di atas dapat disebut a monad
dan kita akan menggunakan simbol yang M[A]
berarti a monad
dengan tipe bagian dalam A
.
Contoh
Beberapa monad yang biasa terlihat meliputi:
List[String]
dimana
M[X] = List[X]
A = String
Option[Int]
dimana
Future[String => Boolean]
dimana
M[X] = Future[X]
A = (String => Boolean)
map dan flatMap
Didefinisikan dalam monad generik M[A]
def map(f: A => B): M[B]
def flatMap(f: A => M[B]): M[B]
misalnya
val list = List("neo", "smith", "trinity")
val f: String => List[Int] = s => s.map(_.toInt).toList
list map f
>> List(List(110, 101, 111), List(115, 109, 105, 116, 104), List(116, 114, 105, 110, 105, 116, 121))
list flatMap f
>> List(110, 101, 111, 115, 109, 105, 116, 104, 116, 114, 105, 110, 105, 116, 121)
untuk ekspresi
Setiap baris dalam ekspresi yang menggunakan <-
simbol diterjemahkan menjadi flatMap
panggilan, kecuali baris terakhir yang diterjemahkan menjadi map
panggilan penutup , di mana "simbol terikat" di sisi kiri diteruskan sebagai parameter ke fungsi argumen (apa kami sebelumnya menelepon f: A => M[B]
):
for {
bound <- list
out <- f(bound)
} yield out
list.flatMap { bound =>
f(bound).map { out =>
out
}
}
list.flatMap { bound =>
f(bound)
}
list flatMap f
Sebuah ekspresi-untuk dengan hanya satu <-
akan diubah menjadi map
panggilan dengan ekspresi yang diteruskan sebagai argumen:
for {
bound <- list
} yield f(bound)
list.map { bound =>
f(bound)
}
list map f
Sekarang langsung ke intinya
Seperti yang Anda lihat, map
operasi tersebut mempertahankan "bentuk" aslinya monad
, sehingga hal yang sama terjadi untuk yield
ekspresi: a List
tetap a List
dengan konten yang diubah oleh operasi di yield
.
Di sisi lain, setiap garis pengikat di dalam for
hanyalah komposisi yang berurutanmonads
, yang harus "diratakan" untuk mempertahankan satu "bentuk luar".
Misalkan sejenak bahwa setiap pengikatan internal diterjemahkan menjadi map
panggilan, tetapi tangan kanan memiliki A => M[B]
fungsi yang sama , Anda akan berakhir dengan a M[M[B]]
untuk setiap baris dalam pemahaman.
Maksud dari keseluruhan for
sintaks adalah untuk dengan mudah "meratakan" rangkaian operasi monadik yang berurutan (yaitu, operasi yang "mengangkat" nilai dalam "bentuk monadik":) A => M[B]
, dengan penambahan map
operasi akhir yang mungkin melakukan transformasi penutup.
Saya harap ini menjelaskan logika di balik pilihan terjemahan, yang diterapkan secara mekanis, yaitu: n
flatMap
panggilan bersarang yang diakhiri dengan satu map
panggilan.
Contoh ilustratif yang dibuat-buat.
Dimaksudkan untuk menunjukkan ekspresi for
sintaksis
case class Customer(value: Int)
case class Consultant(portfolio: List[Customer])
case class Branch(consultants: List[Consultant])
case class Company(branches: List[Branch])
def getCompanyValue(company: Company): Int = {
val valuesList = for {
branch <- company.branches
consultant <- branch.consultants
customer <- consultant.portfolio
} yield (customer.value)
valuesList reduce (_ + _)
}
Bisakah Anda menebak jenisnya valuesList
?
Seperti yang sudah dikatakan, bentuk dari yang monad
dipertahankan melalui pemahaman, jadi kita mulai dengan List
dalam company.branches
, dan harus diakhiri dengan a List
.
Tipe bagian dalam berubah dan ditentukan oleh yield
ekspresi: yaitucustomer.value: Int
valueList
harus menjadi List[Int]
Lists
. Jika Andamap
menggandakan fungsiA => List[B]
(yang merupakan salah satu operasi monadik esensial) pada beberapa nilai, Anda akan mendapatkan List [List [B]] (kami menerima begitu saja bahwa jenisnya cocok). Loop dalam untuk pemahaman menyusun fungsi-fungsi tersebut denganflatMap
operasi yang sesuai , "meratakan" bentuk Daftar [Daftar [B]] menjadi Daftar [B] sederhana ... Saya harap ini jelasyield
klausa tersebutcustomer.value
, yang tipenyaInt
, oleh karena itu keseluruhannyafor comprehension
bernilai aList[Int]
.Saya bukan scala mega mind jadi jangan ragu untuk mengoreksi saya, tapi beginilah cara saya menjelaskan
flatMap/map/for-comprehension
saga ini kepada diri saya sendiri!Untuk memahami
for comprehension
dan menerjemahkannya,scala's map / flatMap
kita harus mengambil langkah kecil dan memahami bagian penyusunnya -map
danflatMap
. Tapi tidakscala's flatMap
hanyamap
denganflatten
Anda bertanya pada diri sendiri! Jika demikian, mengapa begitu banyak pengembang merasa begitu sulit untuk memahaminya atau tentangnyafor-comprehension / flatMap / map
. Nah, jika Anda hanya melihat pada scalamap
danflatMap
tanda tangan Anda melihat mereka mengembalikan tipe pengembalian yang samaM[B]
dan mereka bekerja pada argumen input yang samaA
(setidaknya bagian pertama dari fungsi yang mereka ambil) jika demikian, apa yang membuat perbedaan?Rencana kita
map
.flatMap
.for comprehension
.`Peta Scala
tanda peta skala:
map[B](f: (A) => B): M[B]
Tetapi ada bagian besar yang hilang ketika kita melihat tanda tangan ini, dan ini - dari mana
A
asalnya? penampung kita adalah tipeA
jadi penting untuk melihat fungsi ini dalam konteks penampung -M[A]
. Wadah kita bisa berupaList
item bertipeA
danmap
fungsi kita mengambil fungsi yang mengubah setiap item dari tipeA
menjadi tipeB
, lalu mengembalikan kontainer tipeB
(atauM[B]
)Mari kita tulis tanda tangan peta dengan mempertimbangkan wadah:
M[A]: // We are in M[A] context. map[B](f: (A) => B): M[B] // map takes a function which knows to transform A to B and then it bundles them in M[B]
Catat fakta yang sangat sangat sangat penting tentang map - peta itu otomatis membundel dalam wadah keluaran yang
M[B]
tidak dapat Anda kendalikan. Mari kita tekankan lagi:map
memilih wadah keluaran untuk kita dan itu akan menjadi wadah yang sama dengan sumber yang kita kerjakan untuk ituM[A]
wadah kita mendapatkanM
wadah yang sama hanya untukB
M[B]
dan tidak ada yang lain!map
apakah containerization ini untuk kami, kami hanya memberikan pemetaan dariA
menjadiB
dan itu akan memasukkannya ke dalam kotakM[B]
akan meletakkannya di dalam kotak untuk kita!Anda lihat Anda tidak menentukan bagaimana
containerize
item yang baru saja Anda tentukan bagaimana mengubah item internal. Dan karena kami memiliki wadah yang samaM
untuk keduanyaM[A]
danM[B]
ini berartiM[B]
wadah yang sama, artinya jika Anda memilikinyaList[A]
maka Anda akan memilikiList[B]
dan yang lebih pentingmap
melakukannya untuk Anda!Sekarang kita sudah berurusan dengan
map
mari kita lanjutkan keflatMap
.FlatMap Scala
Mari kita lihat tanda tangannya:
flatMap[B](f: (A) => M[B]): M[B] // we need to show it how to containerize the A into M[B]
Anda melihat perbedaan besar dari map ke
flatMap
di flatMap kami menyediakannya dengan fungsi yang tidak hanya mengonversiA to B
tetapi juga memasukkannya ke dalam containerM[B]
.mengapa kita peduli siapa yang melakukan containerization?
Jadi mengapa kita sangat memperhatikan fungsi input untuk map / flatMap apakah containerization menjadi
M[B]
atau map itu sendiri melakukan containerization untuk kita?Anda lihat dalam konteks
for comprehension
apa yang terjadi adalah beberapa transformasi pada item yang disediakan difor
jadi kami memberikan pekerja berikutnya di jalur perakitan kami kemampuan untuk menentukan kemasan. bayangkan kita memiliki jalur perakitan, setiap pekerja melakukan sesuatu pada produk dan hanya pekerja terakhir yang mengemasnya dalam sebuah wadah! selamat datangflatMap
ini adalah tujuannya, dimap
setiap pekerja ketika selesai mengerjakan item juga mengemasnya sehingga Anda mendapatkan kontainer di atas kontainer.Yang perkasa untuk pemahaman
Sekarang mari kita lihat pemahaman Anda dengan mempertimbangkan apa yang kami katakan di atas:
def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] = for { f <- mkMatcher(pat) g <- mkMatcher(pat2) } yield f(s) && g(s)
Apa yang kita dapatkan disini:
mkMatcher
mengembalikancontainer
wadah berisi fungsi:String => Boolean
<-
mereka terjemahkanflatMap
kecuali yang terakhir.f <- mkMatcher(pat)
yang pertama dalamsequence
(pikirkanassembly line
) semua yang kami inginkan adalah mengambilf
dan meneruskannya ke pekerja berikutnya di jalur perakitan, kami membiarkan pekerja berikutnya di jalur perakitan kami (fungsi selanjutnya) kemampuan untuk menentukan apa yang akan menjadi mengemas kembali barang kami inilah mengapa fungsi terakhir adalahmap
.Yang terakhir
g <- mkMatcher(pat2)
akan menggunakanmap
ini karena yang terakhir di jalur perakitan! sehingga dapat melakukan operasi terakhir denganmap( g =>
ya! menarik keluarg
dan menggunakanf
yang telah ditarik keluar dari wadah olehflatMap
karena itu kita berakhir dengan yang pertama:mkMatcher (pat) flatMap (f // pull out fungsi f memberikan item kepada pekerja lini perakitan berikutnya (Anda melihatnya memiliki akses ke
f
, dan jangan memaketkannya kembali maksud saya biarkan peta menentukan kemasannya biarkan pekerja jalur perakitan berikutnya menentukan container. mkMatcher (pat2) map (g => f (s) ...)) // karena ini adalah fungsi terakhir di jalur perakitan, kita akan menggunakan map dan menarik g keluar dari container dan ke pengemasan kembali , itsmap
dan packaging ini akan mempercepat semua dan menjadi paket atau wadah kami, yah!sumber
Alasannya adalah untuk merantai operasi monad yang memberikan manfaat, penanganan error "gagal cepat" yang tepat.
Ini sebenarnya sangat sederhana. The
mkMatcher
Metode mengembalikan sebuahOption
(yang merupakan Monad). Hasil darimkMatcher
, operasi monadik, adalah aNone
atau aSome(x)
.Menerapkan
map
atauflatMap
fungsi keNone
selalu mengembalikan aNone
- fungsi yang diteruskan sebagai parameter kemap
danflatMap
tidak dievaluasi.Karenanya dalam contoh Anda, jika
mkMatcher(pat)
mengembalikan Tidak Ada, flatMap yang diterapkan padanya akan mengembalikan aNone
(operasi monadik keduamkMatcher(pat2)
tidak akan dijalankan) dan finalmap
akan kembali lagi aNone
. Dengan kata lain, jika salah satu operasi dalam pemahaman for, mengembalikan Tidak ada, Anda memiliki perilaku cepat gagal dan operasi lainnya tidak dijalankan.Ini adalah gaya penanganan error yang monadik. Gaya imperatif menggunakan pengecualian, yang pada dasarnya adalah lompatan (ke klausa tangkapan)
Catatan terakhir:
patterns
fungsinya adalah cara khas untuk "menerjemahkan" penanganan kesalahan gaya imperatif (try
...catch
) ke penanganan kesalahan gaya monadik menggunakanOption
sumber
flatMap
(dan tidakmap
) digunakan untuk "menggabungkan" pemanggilan pertama dan keduamkMatcher
, tetapi mengapamap
(dan tidakflatMap
) digunakan "menggabungkan" yang keduamkMatcher
danyields
blok?flatMap
mengharapkan Anda untuk melewatkan fungsi yang mengembalikan hasil "dibungkus" / diangkat di Monad, sementaramap
akan melakukan pembungkusan / pengangkatan itu sendiri. Selama rangkaian panggilan operasi difor comprehension
Anda perluflatmap
sehingga fungsi yang dilewatkan sebagai parameter dapat kembaliNone
(Anda tidak dapat mengangkat nilai menjadi None). Panggilan operasi terakhir, yangyield
diharapkan untuk menjalankan dan mengembalikan nilai; amap
ke rantai bahwa operasi terakhir sudah cukup dan menghindari harus mengangkat hasil fungsi ke monad.Ini dapat diterjemahkan sebagai:
def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] = for { f <- mkMatcher(pat) // for every element from this [list, array,tuple] g <- mkMatcher(pat2) // iterate through every iteration of pat } yield f(s) && g(s)
Jalankan ini untuk melihat lebih baik bagaimana ini diperluas
def match items(pat:List[Int] ,pat2:List[Char]):Unit = for { f <- pat g <- pat2 } println(f +"->"+g) bothMatch( (1 to 9).toList, ('a' to 'i').toList)
hasilnya adalah:
1 -> a 1 -> b 1 -> c ... 2 -> a 2 -> b ...
Ini mirip dengan
flatMap
- loop melalui setiap elemen dipat
danmap
untuk setiap elemen ke setiap elemen dipat2
sumber
Pertama,
mkMatcher
mengembalikan fungsi yang tanda tangannyaString => Boolean
, itu adalah prosedur java biasa yang baru saja dijalankanPattern.compile(string)
, seperti yang ditunjukkan dipattern
fungsi. Lalu, lihat baris inipattern(pat) map (p => (s:String) => p.matcher(s).matches)
The
map
Fungsi diterapkan pada hasilpattern
, yangOption[Pattern]
, sehinggap
dalamp => xxx
hanya pola Anda dikompilasi. Jadi, dengan adanya polap
, fungsi baru dibangun, yang mengambil Strings
, dan memeriksa apakahs
cocok dengan pola tersebut.(s: String) => p.matcher(s).matches
Catatan,
p
variabel dibatasi ke pola yang dikompilasi. Sekarang, jelas bahwa bagaimana sebuah fungsi dengan tanda tanganString => Boolean
dibangunmkMatcher
.Selanjutnya, mari kita periksa
bothMatch
fungsinya, yang didasarkan padamkMatcher
. Untuk menunjukkan carabothMathch
kerjanya, pertama-tama kita lihat bagian ini:Karena kita mendapat fungsi dengan tanda tangan
String => Boolean
frommkMatcher
, yangg
dalam konteks ini,g(s)
setara denganPattern.compile(pat2).macher(s).matches
, yang mengembalikan jika pola String cocokpat2
. Jadi bagaimanaf(s)
, sama sepertig(s)
, satu-satunya perbedaan adalah, panggilanmkMatcher
penggunaan pertamaflatMap
, bukanmap
, Mengapa? KarenamkMatcher(pat2) map (g => ....)
pengembalianOption[Boolean]
, Anda akan mendapatkan hasil bersarangOption[Option[Boolean]]
jika Anda menggunakanmap
untuk kedua panggilan, bukan itu yang Anda inginkan.sumber