Saya akan menggunakan deskripsi bahasa-agnostik dari monad seperti ini, pertama menggambarkan monoid:
Sebuah monoid adalah (kira-kira) satu set fungsi yang mengambil beberapa jenis sebagai parameter dan mengembalikan tipe yang sama.
Sebuah monad adalah (kira-kira) satu set fungsi yang mengambil pembungkus tipe sebagai parameter dan mengembalikan tipe pembungkus yang sama.
Perhatikan itu adalah deskripsi, bukan definisi. Jangan ragu untuk menyerang deskripsi itu!
Jadi dalam bahasa OO, monad mengizinkan komposisi operasi seperti:
Flier<Duck> m = new Flier<Duck>(duck).takeOff().flyAround().land()
Perhatikan bahwa monad mendefinisikan dan mengontrol semantik operasi tersebut, bukan kelas yang terkandung.
Secara tradisional, dalam bahasa OO kami akan menggunakan hierarki kelas & warisan untuk menyediakan semantik tersebut. Jadi kami akan memiliki Bird
kelas dengan metode takeOff()
, flyAround()
dan land()
, dan Bebek akan mewarisi itu.
Tapi kemudian kita mendapat masalah dengan burung yang tidak bisa terbang, karena penguin.takeOff()
gagal. Kita harus menggunakan lemparan dan penanganan Exception.
Juga, setelah kami mengatakan bahwa Penguin adalah Bird
, kami mengalami masalah dengan banyak pewarisan, misalnya jika kami juga memiliki hierarki Swimmer
.
Pada dasarnya kami mencoba untuk menempatkan kelas ke dalam kategori (dengan permintaan maaf kepada Teori Kategori guys), dan mendefinisikan semantik berdasarkan kategori daripada di kelas individu. Tetapi, monad tampaknya merupakan mekanisme yang jauh lebih jelas untuk melakukan hal itu daripada hierarki.
Jadi dalam hal ini, kita akan memiliki Flier<T>
monad seperti contoh di atas:
Flier<Duck> m = new Flier<Duck>(duck).takeOff().flyAround().land()
... dan kami tidak akan pernah instantiate a Flier<Penguin>
. Kami bahkan dapat menggunakan pengetikan statis untuk mencegah hal itu terjadi, mungkin dengan antarmuka penanda. Atau pengecekan kemampuan runtime untuk menyelamatkan. Tapi sungguh, seorang programmer seharusnya tidak pernah memasukkan Penguin ke Flier, dalam arti yang sama mereka tidak boleh membagi dengan nol.
Juga, ini lebih umum berlaku. Selebaran tidak harus menjadi Burung. Misalnya Flier<Pterodactyl>
, atau Flier<Squirrel>
, tanpa mengubah semantik dari masing-masing tipe tersebut.
Setelah kita mengklasifikasikan semantik berdasarkan fungsi komposable pada wadah - alih-alih dengan hierarki tipe - itu menyelesaikan masalah lama dengan kelas yang "semacam dilakukan, jenis tidak" cocok dengan hierarki tertentu. Ini juga dengan mudah & jelas memungkinkan beberapa semantik untuk suatu kelas, seperti Flier<Duck>
juga Swimmer<Duck>
. Sepertinya kita telah berjuang dengan ketidakcocokan impedansi dengan mengklasifikasikan perilaku dengan hierarki kelas. Monads menanganinya dengan elegan.
Jadi pertanyaan saya adalah, dengan cara yang sama ketika kita lebih menyukai komposisi daripada warisan, apakah masuk akal juga untuk memilih monad daripada warisan?
(BTW saya tidak yakin apakah ini harus ada di sini atau di Comp Sci, tetapi ini tampaknya lebih seperti masalah pemodelan praktis. Tapi mungkin lebih baik di sana.)
sumber
Jawaban:
Jawaban singkatnya adalah tidak , monad bukan alternatif untuk hierarki warisan (juga dikenal sebagai subtipe polimorfisme). Anda tampaknya menggambarkan polimorfisme parametrik , yang digunakan monad tetapi bukan satu-satunya hal yang dapat dilakukan.
Sejauh yang saya mengerti, pada dasarnya monad tidak ada hubungannya dengan warisan. Saya akan mengatakan bahwa kedua hal ini lebih atau kurang orthogonal: mereka dimaksudkan untuk mengatasi masalah yang berbeda, dan karenanya:
Akhirnya, meskipun ini bersinggungan dengan pertanyaan Anda, Anda mungkin tertarik untuk mengetahui bahwa monad memang memiliki beberapa cara yang sangat kuat untuk menyusun; baca tentang transformer monad untuk mencari tahu lebih lanjut. Namun, ini masih merupakan area penelitian aktif karena kami (dan kami, yang saya maksud orang 100000x lebih pintar dari saya) belum menemukan cara yang bagus untuk membuat monad, dan sepertinya beberapa monad tidak menulis secara sewenang-wenang.
Sekarang untuk memilih pertanyaan Anda (maaf, saya bermaksud agar ini membantu, dan tidak membuat Anda merasa buruk): Saya merasa ada banyak tempat yang dipertanyakan yang akan saya coba jelaskan.
Tidak, ini ada
Monad
di Haskell: tipe parameterm a
dengan implementasireturn :: a -> m a
dan(>>=) :: m a -> (a -> m b) -> m b
, memenuhi undang-undang berikut:Ada beberapa contoh Monad yang bukan wadah (
(->) b
), dan ada beberapa wadah yang bukan (dan tidak dapat dibuat) contoh dari Monad (Set
, karena batasan kelas tipe). Jadi intuisi "wadah" adalah yang buruk. Lihat ini untuk lebih banyak contoh.Tidak, tidak sama sekali. Contoh itu tidak memerlukan Monad. Yang diperlukan hanyalah fungsi dengan mencocokkan jenis input dan output. Berikut cara lain untuk menulisnya yang menekankan bahwa itu hanya aplikasi fungsi:
Saya percaya ini adalah pola yang dikenal sebagai "antarmuka yang lancar" atau "metode chaining" (tapi saya tidak yakin).
Tipe data yang juga monad dapat (dan hampir selalu melakukannya!) Memiliki operasi yang tidak terkait dengan monad. Berikut adalah contoh Haskell yang terdiri dari tiga fungsi
[]
yang tidak ada hubungannya dengan monad:[]
"mendefinisikan dan mengontrol semantik operasi" dan "kelas yang terkandung" tidak, tetapi itu tidak cukup untuk membuat monad:Anda telah mencatat dengan benar bahwa ada masalah dengan menggunakan hierarki kelas untuk memodelkan sesuatu. Namun, contoh Anda tidak menunjukkan bukti bahwa monad dapat:
sumber
land(flyAround(takeOff(new Flier<Duck>(duck))))
tidak bekerja (setidaknya dalam OO) karena konstruksi itu membutuhkan pemecahan enkapsulasi untuk mendapatkan rincian Flier. Dengan merantai opertions di kelas, detail Flier tetap tersembunyi, dan itu dapat mempertahankan semantiknya. Itu mirip dengan alasan bahwa di Haskell sebuah monad mengikat(a, M b)
dan tidak(M a, M b)
sehingga monad tidak harus mengekspos keadaannya ke fungsi "aksi".unit
menjadi (kebanyakan) konstruktor pada tipe yang terkandung, danbind
menjadi (sebagian besar) operasi waktu kompilasi tersirat (yaitu mengikat awal) yang mengikat fungsi "aksi" ke kelas. Jika Anda memiliki fungsi kelas satu, atau kelas Function <A, Monad <B>>, makabind
metode dapat melakukan pengikatan terlambat, tapi saya akan mengambil penyalahgunaan itu berikutnya. ;)Flier<Thing>
mengontrol semantik penerbangan, maka itu dapat memaparkan banyak data dan operasi yang memelihara semantik penerbangan, sementara semantik khusus "monad" benar-benar hanya tentang membuatnya semuat rantai dan dirangkum. Kekhawatiran itu mungkin tidak (dan dengan yang saya gunakan, tidak) menyangkut kelas di dalam monad: misalnyaResource<String>
memiliki properti httpStatus, tetapi String tidak.Dalam bahasa non-OO, ya. Dalam bahasa OO yang lebih tradisional, saya akan mengatakan tidak.
Masalahnya adalah bahwa sebagian besar bahasa tidak memiliki spesialisasi jenis, artinya Anda tidak dapat membuat
Flier<Squirrel>
danFlier<Bird>
memiliki implementasi yang berbeda. Anda harus melakukan sesuatu sepertistatic Flier Flier::Create(Squirrel)
(dan kemudian kelebihan untuk setiap jenis). Yang pada gilirannya berarti Anda harus memodifikasi jenis ini setiap kali Anda menambahkan hewan baru, dan kemungkinan menduplikat sedikit kode untuk membuatnya berfungsi.Oh, dan Dalam tidak sedikit bahasa (C # misalnya)
public class Flier<T> : T {}
adalah ilegal. Itu bahkan tidak akan membangun. Sebagian besar, jika tidak semua programmer OO berharapFlier<Bird>
masih menjadiBird
.sumber
Flier<Bird>
merupakan wadah parameter, tidak ada yang akan menganggapnya sebagaiBird
(!?)List<String>
Adalah Daftar bukan String.Flier
bukan hanya sebuah wadah. Jika Anda menganggapnya hanya sebagai wadah, mengapa Anda pernah berpikir bahwa itu bisa menggantikan penggunaan warisan?Animal / Bird / Penguin
biasanya merupakan contoh buruk, karena membawa semua jenis semantik. Contoh praktisnya adalah REST-ish monad yang kami gunakan:Resource<String>.from(uri).get()
Resource
menambahkan semantik di atasString
(atau tipe lain), jadi jelas bukanString
.