Halo semuanya.
Setelah empat tahun pemrograman pada Scala, pemahaman saya tentang monad akhirnya berkembang ke titik di mana Anda dapat menjelaskannya kepada orang lain tanpa mengacu pada teori kategori dan
monad klasik
- itu hanya monoid dalam kategori endofunders , yang membuat takut programmer tidak lebih buruk daripada kecoak dichlorvos.
Contoh kode akan ditulis di Kotlin, sebagai itu cukup populer, dan pada saat yang sama cukup fungsional (dalam kedua pengertian kata).
Mari kita mulai dengan konsep
functor , ini dia:
interface Functor<A>
Apa maknanya? Functor adalah abstraksi dari perhitungan arbitrer yang mengembalikan hasil tipe A. Kita mengabaikan cara membuat functor baru, dan, yang paling penting, bagaimana menghitung nilainya A. Secara khusus, fungsi dapat bersembunyi di balik antarmuka functor dengan sejumlah argumen sewenang-wenang, dan belum tentu fungsi murni.
Contoh implementasi functor:
- konstan
- berfungsi dengan sejumlah argumen arbitrer yang mengembalikan hasil tipe
A
- state pseudo-random generator (Acak)
- generator nomor acak perangkat keras
- membaca objek dari disk atau dari jaringan
- perhitungan asinkron - panggilan balik dilewatkan ke implementasi functor, yang akan dipanggil beberapa waktu kemudian
Semua contoh ini, kecuali untuk konstanta, memiliki satu properti penting - mereka malas, mis. perhitungan itu sendiri tidak terjadi ketika functor dibuat, tetapi ketika dihitung.
Antarmuka functor tidak memungkinkan mendapatkan nilai tipe
A
dari
Functor<A>
, atau membuat
Functor<A>
baru
Functor<A>
dari nilai tipe
A
Tetapi bahkan dengan pembatasan seperti itu, functor tidak sia-sia - jika untuk beberapa tipe
B
kita dapat mengkonversi
A
ke
B
(dengan kata lain, ada fungsi
(a: A) -> B
), maka kita dapat menulis fungsi
(f: Functor<A>) -> Functor<B>
dan
(f: Functor<A>) -> Functor<B>
nama
map
:
interface Functor<A> { fun <B> map(f: (A) -> B): Functor<B> }
Berbeda dengan functor itu sendiri, metode peta tidak bisa menjadi fungsi arbitrer:
-
map((a) -> a)
harus mengembalikan functor yang sama
-
map((a) -> f(a)).map((b) -> g(b))
harus identik dengan
map(a -> g(f(a))
Sebagai contoh, kami mengimplementasikan functor yang mengembalikan nilai A yang mengandung sejumlah bit acak. Antarmuka kami di Kotlin tidak dapat digunakan dengan mudah (tetapi Anda
dapat , jika diinginkan), jadi kami akan menulis metode ekstensi:
Contoh lain dari functors dengan
map
berguna
List<A>
abadi List<A>
MyInputStream<A>
Optional<A>
Sekarang kamu bisa pergi ke monads.
Monad adalah functor dengan dua operasi tambahan. Pertama-tama, monad, tidak seperti functor, berisi operasi penciptaan dari sebuah konstanta, operasi ini disebut
lift
:
fun <A> lift(value: A): Monad<A> = TODO()
Operasi kedua disebut
flatMap
, ini lebih rumit, jadi pertama-tama kami akan memberikan seluruh antarmuka monad kami:
interface Monad<A> {
Perbedaan paling penting antara monad dan functor adalah bahwa monad dapat
dikombinasikan satu sama lain, menghasilkan monad baru dan abstrak dari bagaimana monad diimplementasikan - apakah itu membaca dari disk, apakah itu menerima parameter tambahan untuk menghitung nilainya, apakah nilai ini ada . Poin penting kedua - monad tidak digabungkan secara paralel, tetapi secara berurutan, meninggalkan kemampuan untuk menambahkan logika tergantung pada hasil dari monad pertama.
Contoh:
Namun, dalam contoh ini tidak disebutkan jaringan. Sama baiknya, data dapat dibaca dari file atau dari database. Mereka dapat dibaca secara sinkron atau asinkron, di sini mungkin ada penanganan kesalahan - semuanya tergantung pada implementasi spesifik dari monad, kode itu sendiri akan tetap tidak berubah.
Pada awalnya contohnya lebih sederhana, Opsi monad. Di kotlin, itu tidak benar-benar dibutuhkan, tetapi di Jawa / Scala sangat berguna:
data class Option<A>(val value: A?) { fun <B> map(f: (A) -> B): Option<B> = flatMap { a -> lift(f(a)) } fun <B> flatMap(f: (A) -> Option<B>): Option<B> = when(value) { null -> Option(null) else -> f(value) } } fun <A> lift(value: A?): Option<A> = Option(value) fun mul(a: Option<Int>, b: Option<Int>): Option<Int> = a.flatMap { a -> b.map { b -> a * b } } fun main(args: Array<String>) { println(mul(Option(4), Option(5)).value)
Sebagai monad pozakovyristy, mari kita selesaikan pekerjaan basis data dengan monad:
data class DB<A>(val f: (Connection) -> A) { fun <B> map(f: (A) -> B): DB<B> = flatMap { a -> lift(f(a)) } fun <B> flatMap(f: (A) -> DB<B>): DB<B> = DB { conn -> f(this.f(conn)).f(conn) } } fun <A> lift(value: A): DB<A> = DB { value } fun select(id: Int): DB<String> = DB { conn -> val st = conn.createStatement()
Apakah lubang kelinci dalam?
Ada berbagai macam monad, tetapi tujuan utamanya adalah untuk mengabstraksi logika bisnis aplikasi dari beberapa perincian perhitungan yang dilakukan:
- bahwa nilainya mungkin tidak ada:
data class Option<A>(value: A?)
- bahwa perhitungan akan gagal:
data class Either<Error, A>(value: Pair<Error?, A?>)
- bahwa perhitungannya bisa malas:
data class Defer<A>(value: () -> A)
- atau asinkron:
java.util.concurrent.CompletableFuture<A>
- atau memiliki status fungsional:
data class State<S, A>(value: (S) -> Pair<S, A>)
Status data class State<S, A>(value: (S) -> Pair<S, A>)
Daftar pertanyaan yang belum terjawab:
- functors aplikatif - tautan perantara antara functors dan monads
- koleksi seperti monad
- komposisi monotipe monad - panah gluesi, transformator monadik
- urutan / lintasan
- Monad sebagai efek
- monad dan rekursi, stack overflow, trampolining
- Penyandian akhir tanpa tag
- Io monad
- dan umumnya seluruh kebun binatang monad standar
Apa selanjutnya
arrow-kt.iotypelevel.org/cats/typeclasses.htmlwiki.haskell.org/All_About_MonadsEksperimen saya adalah aplikasi gaya FP lengkap di Scala:
github.com/scf37/fpscala2PS Saya ingin catatan kecil, ternyata seperti biasa.