Bab 09: Monadic Onions
Pointy Functor Factory
Sebelum kita melangkah lebih jauh, saya harus membuat pengakuan: Saya belum sepenuhnya jujur tentang method of
yang telah kami tempatkan pada masing-masing tipe kami.
Ternyata, itu tidak ada untuk menghindari kata kunci new
, melainkan untuk menempatkan nilai dalam apa yang disebut default minimal context.
Ya, of
sebenarnya tidak menggantikan konstruktor - ini adalah bagian dari antarmuka penting yang kami sebut Pointed.
pointed functor adalah sebuah functor dengan method
of
Yang penting di sini adalah kemampuan untuk menjatuhkan nilai apa pun dalam tipe kita dan mulai memetakan.
Jika Anda ingat, konstruktor IO
dan Task
mengharapkan fungsi sebagai argumen mereka, tetapi Maybe
dan Either
tidak.
Motivasi untuk antarmuka ini adalah cara yang umum dan konsisten untuk menempatkan nilai ke dalam functor kami tanpa kerumitan dan tuntutan khusus dari konstruktor.
Istilah default minimal context kurang presisi, namun menangkap ide dengan baik: kami ingin mengangkat nilai apa pun dalam tipe kami dan map
seperti biasa dengan perilaku yang diharapkan dari fungsi mana pun.
Salah satu koreksi penting yang harus saya buat pada titik ini, permainan kata-kata, itu adalah Left.of
tidak masuk akal.
Setiap functor harus memiliki satu cara untuk menempatkan nilai di dalamnya dengan Either
, yaitu new Right(x)
. Kami mendefinisikan of
menggunakan Right
karena jika tipe kami bisa map
, seharusnya map
.
Melihat contoh di atas, kita harus memiliki intuisi tentang bagaimana of
biasanya akan bekerja dan Left
memecahkan cetakan itu.
Anda mungkin pernah mendengar fungsi seperti pure
, point
, unit
, dan return
. Ini adalah berbagai moniker untuk method of
, fungsi internasional misteri.
of
akan menjadi penting ketika kita mulai menggunakan monad karena seperti yang akan kita lihat, adalah tanggung jawab kita untuk menempatkan nilai kembali ke dalam tipe secara manual.
Untuk menghindari kata kunci new
, ada beberapa trik atau pustaka JavaScript standar jadi mari kita gunakan dan gunakan of
seperti orang dewasa yang bertanggung jawab mulai sekarang.
Saya sarankan menggunakan instance functor dari folktale, ramda atau fantasy-land karena mereka menyediakan method of
yang benar serta konstruktor bagus yang tidak bergantung pada new
.
Mencampur Metafora
Anda tahu, selain burrito luar angkasa (jika Anda pernah mendengar desas-desus), monad itu seperti bawang. Izinkan saya untuk menunjukkan dengan situasi umum:
Apa yang kita dapatkan di sini adalah IO
terperangkap di dalam IO
lain karena print
diperkenalkan IO kedua selama map
.
Untuk terus bekerja dengan string kita, kita harus map(map(f))
dan untuk mengamati efeknya, kita harus unsafePerformIO().unsafePerformIO()
.
Meskipun senang melihat bahwa kami memiliki dua efek yang dikemas dan siap digunakan dalam aplikasi kami, rasanya seperti bekerja dalam dua setelan hazmat dan kami berakhir dengan API yang canggung dan tidak nyaman.
Mari kita lihat situasi lain:
Sekali lagi, kita melihat situasi functor bersarang ini di mana rapi untuk melihat ada tiga kemungkinan kegagalan dalam fungsi kita, tetapi agak lancang untuk mengharapkan pemanggil map
tiga kali untuk mendapatkan nilai - kita baru saja bertemu.
Pola ini akan muncul berulang kali dan ini adalah situasi utama di mana kita perlu menyinari simbol monad yang perkasa ke langit malam.
Saya mengatakan monad seperti bawang karena air mata keluar saat kita mengupas setiap lapisan functor bersarang dengan map
untuk mendapatkan nilai bagian dalamnya.
Kita bisa mengeringkan mata, mengambil napas dalam-dalam, dan menggunakan method yang disebut join.
Jika kita memiliki dua lapisan dengan tipe yang sama, kita dapat menghancurkannya bersama-sama dengan join
. Kemampuan untuk bergabung bersama, perkawinan functor inilah yang membuat monad menjadi monad.
Mari kita beringsut menuju definisi lengkap dengan sesuatu yang sedikit lebih akurat:
Monad adalah pointed functors yang dapat meratakan
Setiap fungsi yang mendefinisikan method join, memiliki method of, dan mematuhi beberapa hukum adalah monad.
Mendefinisikan join
tidak terlalu sulit jadi mari kita lakukan untuk Maybe
:
Di sana, sederhana seperti mengonsumsi saudara kembarnya di dalam kandungan. Jika kita memilikiMaybe(Maybe(x))
maka .$value
hanya akan menghapus lapisan tambahan yang tidak perlu dan kita dapat map
dengan aman dari sana.
Jika tidak, kita hanya akan memiliki satu Maybe
karena tidak ada yang akan dipetakan sejak awal.
Sekarang setelah kita memiliki method join, mari taburkan debu monad ajaib di atas firstAddressStreet
contoh dan lihat aksinya:
Kami menambahkan di join
mana pun kami menemukan sarang Maybe
agar tidak lepas kendali. Mari kita lakukan hal yang sama dengan IO
.
Sekali lagi, kita cukup menghapus satu layer. Ingat, kami tidak membuang kemurnian, tetapi hanya menghapus satu lapis bungkus susut berlebih.
getItem
mengembalikan IO String
jadi map
menguraikannya. log
dan setStyle
mengembalikan IO
sendiri jadi kita harus join
untuk menjaga sarang kita tetap terkendali.
Rantaiku Memukul Dadaku
Anda mungkin telah memperhatikan sebuah pola. Kami sering berakhir memanggil join
tepat setelah map
. Mari kita abstraksikan ini menjadi fungsi yang disebut chain
.
Kami hanya akan menggabungkan kombo map/join ini menjadi satu fungsi.
Jika Anda pernah membaca tentang monad sebelumnya, Anda mungkin pernah melihat chain
disebut >>=
(diucapkan mengikat) atau flatMap
yang semuanya alias untuk konsep yang sama.
Menurut saya pribadi flatMap
adalah nama yang paling akurat, tetapi kami akan tetap menggunakan chain
karena itu adalah nama yang diterima secara luas di JS.
Mari kita refactor dua contoh di atas dengan chain
:
Saya menukar map
/join
dengan fungsi chain
baru kami untuk sedikit merapikannya.
Semuanya bersih dan bagus, tetapi ada lebih dari chain
yang terlihat - ini lebih seperti tornado daripada ruang hampa. Karena efek chain
bersarang yang mudah, kami dapat menangkap urutan dan penugasan variabel dengan cara yang murni fungsional.
Kita dapat menulis contoh-contoh ini dengan compose
, tetapi kita memerlukan beberapa fungsi pembantu dan gaya ini cocok untuk penugasan variabel eksplisit melalui closures.
Alih-alih, kami menggunakan versi infiks chain
yang kebetulan dapat diturunkan dari map
dan join
untuk jenis apa pun secara otomatis: t.prototype.chain = function(f) { return this.map(f).join(); }
.
Kita juga dapat mendefinisikan chain
secara manual jika kita menginginkan rasa kinerja yang salah, meskipun kita harus berhati-hati untuk mempertahankan fungsionalitas yang benar - yaitu, harus sama map
diikuti oleh join
.
Fakta yang menarik adalah bahwa kita dapat memperoleh map
secara gratis jika kita telah membuat chain
hanya dengan membotolkan nilai kembali ketika kita selesai dengan of
.
Dengan chain
, kita juga dapat mendefinisikan join
sebagai chain(id)
. Ini mungkin terasa seperti bermain Texas Hold'em dengan pesulap berlian imitasi karena saya hanya menarik sesuatu dari belakang saya.
Tetapi seperti kebanyakan matematika, semua konstruksi berprinsip ini saling terkait. Banyak dari derivasi ini disebutkan dalam repo fantasyland, yang merupakan spesifikasi resmi untuk tipe data aljabar dalam JavaScript.
Ngomong-ngomong, mari kita ke contoh di atas.
Dalam contoh pertama, kita melihat dua Task
dirantai dalam urutan tindakan asinkron - pertama ia mengambil user
, kemudian menemukan teman dengan id pengguna itu. Kita gunakan chain
untuk menghindari suatu situasi Task(Task([Friend]))
.
Selanjutnya, kita gunakan querySelector
untuk menemukan beberapa input berbeda dan membuat pesan sambutan.
Perhatikan bagaimana kita memiliki akses ke keduanya uname
dan email
pada fungsi terdalam - ini adalah penugasan variabel fungsional yang terbaik.
Karena IO
dengan murah hati meminjamkan nilainya kepada kami, kami bertanggung jawab untuk mengembalikannya seperti yang kami temukan - kami tidak ingin merusak kepercayaannya (dan program kami).
IO.of
adalah alat yang sempurna untuk pekerjaan itu dan itulah sebabnya Pointed merupakan prasyarat penting untuk antarmuka Monad.
Namun, kami dapat memilih map
karena itu juga akan mengembalikan jenis yang benar:
Akhirnya, kami memiliki dua contoh penggunaan Maybe
. Karena pemetaan chain
di bawah tenda, jika ada nilai null
, kami menghentikan perhitungan mati di jalurnya.
Jangan khawatir jika contoh-contoh ini sulit dipahami pada awalnya. Bermain dengan mereka. Tusuk mereka dengan tongkat. Hancurkan mereka hingga berkeping-keping dan pasang kembali.
Ingatlah saat map
mengembalikan nilai "normal" dan saat chain
mengembalikan fungsi lain. Di bab berikutnya, kita akan mendekati Applicatives
dan melihat trik yang bagus untuk membuat ekspresi semacam ini lebih bagus dan mudah dibaca.
Sebagai pengingat, ini tidak berfungsi dengan dua jenis bersarang yang berbeda. Komposisi functor dan transformer monad, dapat membantu kita dalam situasi itu.
Power Trip
Pemrograman gaya kontainer terkadang bisa membingungkan. Terkadang kita menemukan diri kita berjuang untuk memahami berapa banyak container dalam suatu nilai atau jika kita membutuhkan map
atau chain
(segera kita akan melihat lebih banyak method container).
Kami dapat sangat meningkatkan debugging dengan trik seperti menerapkan inspect
dan kami akan belajar cara membuat "tumpukan" yang dapat menangani efek apa pun yang kami berikan, tetapi ada kalanya kami mempertanyakan apakah itu sepadan dengan kerumitannya.
Saya ingin mengayunkan pedang monadik yang berapi-api sejenak untuk menunjukkan kekuatan pemrograman dengan cara ini.
Mari kita baca file, lalu unggah langsung setelahnya:
Di sini, kami mencabangkan kode kami beberapa kali. Melihat tanda tangan tipe, saya dapat melihat bahwa kami melindungi dari 3 kesalahan - penggunaan readFile
untuk Either
memvalidasi input (mungkin memastikan nama file ada).
readFile
mungkin mendapatkan kesalahan saat mengakses file seperti yang dinyatakan dalam parameter tipe pertama Task
,
Dan unggahan mungkin gagal untuk alasan apapun yang diungkapkan oleh Error
di httpPost
. Kami dengan santai melakukan dua tindakan asinkron berurutan bersarang dengan chain
.
Semua ini dicapai dalam satu aliran linier kiri ke kanan. Ini semua murni dan deklaratif.
Ini memegang penalaran persamaan dan sifat yang dapat diandalkan. Kami tidak dipaksa untuk menambahkan nama variabel yang tidak perlu dan membingungkan.
Fungsi upload
kami ditulis sesuai antarmuka generik dan bukan api khusus satu kali. Ini satu garis berdarah demi Tuhan.
Sebagai kontras, mari kita lihat cara imperatif standar untuk melakukan ini:
Bukankah itu aritmatika iblis. Kami terjepit di labirin kegilaan yang bergejolak.
Bayangkan jika itu adalah aplikasi tipikal yang juga mengubah variabel di sepanjang jalan! Kami benar-benar akan berada di lubang tar.
Teori
Hukum pertama yang akan kita lihat adalah asosiatif, tetapi mungkin tidak seperti yang biasa Anda lakukan.
Hukum-hukum ini mendapatkan sifat monad yang bersarang sehingga associativity berfokus pada menggabungkan tipe dalam atau luar terlebih dahulu untuk mencapai hasil yang sama. Gambar mungkin lebih instruktif:
Dimulai dengan bagian atas kiri bergerak ke bawah, kita bisa join
luar dua M
dari M(M(M a))
pertama maka kapal pesiar diinginkan M a
dengan join
yang lain.
Atau, kita dapat membuka tudung dan meratakan dua bagian dalam M
dengan map(join)
. Kita berakhir dengan hal yang sama M a
terlepas dari apakah kita bergabung dengan bagian dalam atau luar M
terlebih dahulu dan itulah yang dimaksud dengan asosiatif.
Perlu dicatat bahwa map(join) != join
. Langkah-langkah perantara dapat bervariasi nilainya, tetapi hasil akhir dari langkah terakhir join
akan sama.
Hukum kedua serupa:
Ini menyatakan bahwa, untuk setiap monad M
, of
dan join
berjumlah id
. Kita juga bisa map(of)
dan menyerangnya dari dalam ke luar. Kami menyebutnya "triangle identity" karena membuat bentuk seperti itu ketika divisualisasikan.
Jika kita mulai dari kiri atas menuju kanan, kita dapat melihat bahwa of
memang menjatuhkan M a
di wadah M
lain. Kemudian jika kita bergerak ke bawah dan itu join
, kita mendapatkan yang sama seolah-olah kita baru saja memanggil id
di tempat pertama.
Bergerak dari kanan ke kiri, kita melihat bahwa jika kita menyelinap di bawah selimut dengan map
dan memanggil of
dari a
mentah, kita masih akan berakhir M (M a)
dan join
akan membawa kita kembali ke titik awal.
Saya harus menyebutkan bahwa saya baru saja menulis of
, bagaimanapun, itu harus spesifik M.of
untuk monad apa pun yang kami gunakan.
Sekarang, saya telah melihat hukum, identitas, dan asosiasi ini, di suatu tempat sebelumnya...
Tunggu, saya sedang berpikir... Ya tentu saja! Mereka adalah hukum untuk suatu kategori.
Tapi itu berarti kita membutuhkan fungsi komposisi untuk melengkapi definisi.
Lihat:
Bagaimanapun, mereka adalah hukum kategori.
Monad membentuk kategori yang disebut "kategori Kleisli" di mana semua objek adalah monad dan morfisme adalah fungsi yang di-chain.
Saya tidak bermaksud mengejek Anda dengan teori kategori bit dan bobs tanpa banyak penjelasan tentang bagaimana jigsaw cocok bersama.
Tujuannya adalah untuk menggores permukaan cukup untuk menunjukkan relevansi dan memicu minat sambil berfokus pada sifat praktis yang dapat kita gunakan setiap hari.
Singkatnya
Monad memungkinkan kita menelusuri ke bawah ke dalam perhitungan bersarang. Kita dapat menetapkan variabel, menjalankan efek sekuensial, melakukan tugas asinkron, semua tanpa meletakkan satu bata di piramida malapetaka.
Mereka datang untuk menyelamatkan ketika suatu nilai menemukan dirinya dipenjara di beberapa lapisan dengan jenis yang sama. Dengan bantuan sidekick tepercaya "pointed", monads dapat memberi kami nilai tanpa kotak dan tahu kami akan dapat menempatkannya kembali ketika kami selesai.
Ya, monad sangat kuat, namun kami masih membutuhkan beberapa fungsi container tambahan.
Misalnya, bagaimana jika kita ingin menjalankan daftar panggilan api sekaligus, lalu mengumpulkan hasilnya? Kita dapat menyelesaikan tugas ini dengan monad, tetapi kita harus menunggu setiap monad selesai sebelum memanggil monad berikutnya.
Bagaimana dengan menggabungkan beberapa validasi? Kami ingin melanjutkan validasi untuk mengumpulkan daftar kesalahan, tetapi monad akan menghentikan pertunjukan setelah yang pertama Left
memasuki gambar.
Di bab berikutnya, kita akan melihat bagaimana fungsi aplikatif cocok dengan dunia container dan mengapa kita lebih memilih mereka daripada monad dalam banyak kasus.
Latihan
Mempertimbangkan objek Pengguna sebagai berikut:
Gunakan safeProp
dan map
/join
atau chain
untuk mendapatkan nama jalan dengan aman saat diberikan kepada pengguna
Kami sekarang mempertimbangkan item berikut:
Untuk latihan ini, kami mempertimbangkan pembantu dengan tanda tangan berikut:
Last updated