Monad dalam bahasa Inggris biasa? (Untuk programmer OOP tanpa latar belakang FP)

743

Dalam hal yang dimengerti oleh programmer OOP (tanpa latar belakang pemrograman fungsional), apa itu monad?

Masalah apa yang dipecahkan dan tempat apa yang paling umum digunakan?

EDIT:

Untuk memperjelas jenis pemahaman yang saya cari, katakanlah Anda mengubah aplikasi FP yang memiliki monads menjadi aplikasi OOP. Apa yang akan Anda lakukan untuk memindahkan tanggung jawab monad ke aplikasi OOP?


sumber
10
Posting blog ini sangat bagus: blog.sigfpe.com/2006/08/you-could-have-invented-monads-and.html
Pascal Cuoq
10
@Pavel: Jawaban yang kami dapatkan di bawah dari Eric jauh lebih baik daripada yang ada di Q lainnya yang disarankan untuk orang-orang dengan latar belakang OO (sebagai lawan dari latar belakang FP).
Donal Fellows
5
@ Donal: Jika ini adalah penipuan (tentang yang saya tidak memiliki pendapat), jawaban yang baik harus ditambahkan ke aslinya. Yaitu: jawaban yang baik tidak menghalangi penutupan sebagai duplikat. Jika itu adalah duplikat yang cukup dekat, ini dapat dilakukan oleh moderator sebagai penggabungan.
dmckee --- ex-moderator kitten

Jawaban:

732

UPDATE: Pertanyaan ini adalah subjek dari seri blog yang sangat panjang, yang dapat Anda baca di Monads - terima kasih atas pertanyaannya!

Dalam hal yang dimengerti oleh programmer OOP (tanpa latar belakang pemrograman fungsional), apa itu monad?

Monad adalah "penguat" jenis yang mematuhi aturan tertentu dan yang memiliki operasi tertentu disediakan .

Pertama, apa yang dimaksud dengan "penguat tipe"? Maksud saya beberapa sistem yang memungkinkan Anda mengambil jenis dan mengubahnya menjadi jenis yang lebih khusus. Sebagai contoh, dalam C # pertimbangkan Nullable<T>. Ini adalah penguat jenis. Itu memungkinkan Anda mengambil suatu tipe, mengatakan int, dan menambahkan kemampuan baru ke tipe itu, yaitu, bahwa sekarang ia bisa menjadi nol ketika sebelumnya tidak bisa.

Sebagai contoh kedua, pertimbangkan IEnumerable<T>. Ini adalah penguat jenis. Ini memungkinkan Anda mengambil suatu jenis, mengatakan,, stringdan menambahkan kemampuan baru ke jenis itu, yaitu, bahwa Anda sekarang dapat membuat urutan string dari sejumlah string tunggal.

Apa "aturan tertentu"? Secara singkat, bahwa ada cara yang masuk akal untuk fungsi pada tipe yang mendasarinya untuk bekerja pada tipe yang diperkuat sehingga mereka mengikuti aturan normal komposisi fungsional. Misalnya, jika Anda memiliki fungsi pada bilangan bulat, katakanlah

int M(int x) { return x + N(x * 2); }

maka fungsi yang sesuai pada Nullable<int>dapat membuat semua operator dan panggilan di sana bekerja bersama "dengan cara yang sama" yang mereka lakukan sebelumnya.

(Itu sangat kabur dan tidak tepat; Anda meminta penjelasan yang tidak berasumsi tentang pengetahuan komposisi fungsional.)

Apa itu "operasi"?

  1. Ada operasi "unit" (membingungkan kadang-kadang disebut operasi "kembali") yang mengambil nilai dari tipe polos dan menciptakan nilai monadik yang setara. Ini, pada dasarnya, menyediakan cara untuk mengambil nilai dari tipe yang tidak teramplifikasi dan mengubahnya menjadi nilai dari tipe yang diamplifikasi. Ini dapat diimplementasikan sebagai konstruktor dalam bahasa OO.

  2. Ada operasi "bind" yang mengambil nilai monadik dan fungsi yang dapat mengubah nilai, dan mengembalikan nilai monadik baru. Bind adalah operasi utama yang mendefinisikan semantik monad. Ini memungkinkan kita mengubah operasi pada tipe yang tidak teramplifikasi menjadi operasi pada tipe yang diamplifikasi, yang mematuhi aturan komposisi fungsional yang disebutkan sebelumnya.

  3. Sering ada cara untuk mendapatkan kembali tipe yang tidak teramplifikasi dari tipe yang diamplifikasi. Tegasnya operasi ini tidak wajib memiliki monad. (Meskipun perlu jika Anda ingin memiliki comonad . Kami tidak akan mempertimbangkan yang lebih lanjut dalam artikel ini.)

Sekali lagi, ambil Nullable<T>sebagai contoh. Anda dapat mengubah sebuah intmenjadi Nullable<int>dengan konstruktor. Kompiler C # menangani "lifting" yang paling dapat dibatalkan untuk Anda, tetapi jika tidak, transformasi pengangkatannya mudah: sebuah operasi, katakanlah,

int M(int x) { whatever }

ditransformasikan menjadi

Nullable<int> M(Nullable<int> x) 
{ 
    if (x == null) 
        return null; 
    else 
        return new Nullable<int>(whatever);
}

Dan mengubah Nullable<int>kembali menjadi intselesai dengan Valueproperti.

Ini adalah transformasi fungsi yang merupakan bit kunci. Perhatikan bagaimana semantik sebenarnya dari operasi yang dapat dibatalkan - bahwa operasi pada nullpropagasi null- ditangkap dalam transformasi. Kita bisa menggeneralisasi ini.

Misalkan Anda memiliki fungsi dari inthingga int, seperti aslinya kami M. Anda dapat dengan mudah menjadikannya sebagai fungsi yang mengambil intdan mengembalikan a Nullable<int>karena Anda bisa menjalankan hasilnya melalui konstruktor yang dapat dibatalkan. Sekarang anggaplah Anda memiliki metode tingkat tinggi ini:

static Nullable<T> Bind<T>(Nullable<T> amplified, Func<T, Nullable<T>> func)
{
    if (amplified == null) 
        return null;
    else
        return func(amplified.Value);
}

Lihat apa yang bisa Anda lakukan dengan itu? Setiap metode yang mengambil intdan mengembalikan sebuah int, atau mengambil intdan mengembalikan Nullable<int>sekarang dapat memiliki semantik nullable diterapkan untuk itu .

Selanjutnya: misalkan Anda memiliki dua metode

Nullable<int> X(int q) { ... }
Nullable<int> Y(int r) { ... }

dan Anda ingin membuatnya:

Nullable<int> Z(int s) { return X(Y(s)); }

Artinya, Zadalah komposisi Xdan Y. Tetapi Anda tidak dapat melakukannya karena Xmembutuhkan int, dan Ymengembalikan a Nullable<int>. Tetapi karena Anda memiliki operasi "bind", Anda dapat membuatnya bekerja:

Nullable<int> Z(int s) { return Bind(Y(s), X); }

Operasi mengikat pada monad adalah apa yang membuat komposisi fungsi pada jenis yang diperkuat bekerja. "Aturan" yang saya berikan di atas adalah bahwa monad mempertahankan aturan komposisi fungsi normal; yang menyusun dengan fungsi identitas menghasilkan fungsi asli, komposisi itu asosiatif, dan sebagainya.

Dalam C #, "Bind" disebut "SelectMany". Lihatlah cara kerjanya pada urutan monad. Kita perlu memiliki dua hal: mengubah nilai menjadi urutan dan mengikat operasi pada urutan. Sebagai bonus, kami juga memiliki "mengubah urutan kembali menjadi nilai". Operasi tersebut adalah:

static IEnumerable<T> MakeSequence<T>(T item)
{
    yield return item;
}
// Extract a value
static T First<T>(IEnumerable<T> sequence)
{
    // let's just take the first one
    foreach(T item in sequence) return item; 
    throw new Exception("No first item");
}
// "Bind" is called "SelectMany"
static IEnumerable<T> SelectMany<T>(IEnumerable<T> seq, Func<T, IEnumerable<T>> func)
{
    foreach(T item in seq)
        foreach(T result in func(item))
            yield return result;            
}

Aturan monad yang dapat dibatalkan adalah "untuk menggabungkan dua fungsi yang menghasilkan nullables bersama, periksa untuk melihat apakah bagian dalam menghasilkan nol; jika itu, menghasilkan nol, jika tidak, maka panggil bagian luar dengan hasilnya". Itu semantik yang diinginkan dari nullable.

Aturan urutan monad adalah "untuk menggabungkan dua fungsi yang menghasilkan urutan bersama, menerapkan fungsi luar untuk setiap elemen yang dihasilkan oleh fungsi bagian dalam, dan kemudian menggabungkan semua urutan yang dihasilkan bersama-sama". Semantik dasar dari monad ditangkap dalam Bind/ SelectManymetode; ini adalah metode yang memberitahu Anda apa yang monad benar-benar berarti .

Kita bisa melakukan yang lebih baik lagi. Misalkan Anda memiliki urutan int, dan metode yang mengambil int dan menghasilkan urutan string. Kita dapat menggeneralisasi operasi pengikatan untuk memungkinkan komposisi fungsi yang mengambil dan mengembalikan berbagai jenis yang diamplifikasi, selama input dari satu cocok dengan output dari yang lain:

static IEnumerable<U> SelectMany<T,U>(IEnumerable<T> seq, Func<T, IEnumerable<U>> func)
{
    foreach(T item in seq)
        foreach(U result in func(item))
            yield return result;            
}

Jadi sekarang kita dapat mengatakan "perkuat kumpulan bilangan bulat individual ini menjadi urutan bilangan bulat. Ubah bilangan bulat khusus ini menjadi sekelompok string, diamplifikasi ke urutan string. Sekarang satukan kedua operasi bersama: perkuat kumpulan integer ini ke dalam gabungan dari semua urutan string. " Monads memungkinkan Anda membuat amplifikasi.

Masalah apa yang dipecahkan dan tempat apa yang paling umum digunakan?

Itu agak seperti bertanya "masalah apa yang dipecahkan oleh pola singleton?", Tetapi saya akan mencobanya.

Monads biasanya digunakan untuk menyelesaikan masalah seperti:

  • Saya perlu membuat kemampuan baru untuk tipe ini dan masih menggabungkan fungsi lama pada tipe ini untuk menggunakan kemampuan baru.
  • Saya perlu menangkap banyak operasi pada jenis dan mewakili operasi tersebut sebagai objek yang dapat dikomposisikan, membangun komposisi yang lebih besar dan lebih besar sampai saya memiliki rangkaian operasi yang tepat yang diwakili, dan kemudian saya harus mulai mendapatkan hasil dari hal tersebut.
  • Saya perlu mewakili operasi efek samping secara bersih dalam bahasa yang membenci efek samping

C # menggunakan monads dalam desainnya. Seperti yang telah disebutkan, pola nullable sangat mirip dengan "mungkin monad". LINQ seluruhnya dibangun dari monad; yang SelectManymetode adalah apa cara kerja semantik komposisi operasi. (Erik Meijer gemar menunjukkan bahwa setiap fungsi LINQ sebenarnya dapat diimplementasikan oleh SelectMany; segala sesuatu yang lain hanya kenyamanan.)

Untuk memperjelas jenis pemahaman yang saya cari, katakanlah Anda mengubah aplikasi FP yang memiliki monads menjadi aplikasi OOP. Apa yang akan Anda lakukan untuk memindahkan tanggung jawab monad ke dalam aplikasi OOP?

Sebagian besar bahasa OOP tidak memiliki sistem tipe yang cukup kaya untuk mewakili pola monad itu sendiri secara langsung; Anda memerlukan sistem tipe yang mendukung tipe yang tipe lebih tinggi daripada tipe generik. Jadi saya tidak akan mencoba melakukan itu. Sebaliknya, saya akan menerapkan tipe generik yang mewakili masing-masing monad, dan menerapkan metode yang mewakili tiga operasi yang Anda butuhkan: mengubah nilai menjadi nilai yang diperkuat, (mungkin) mengubah nilai yang diperkuat menjadi nilai, dan mengubah fungsi pada nilai yang tidak teramplifikasi menjadi nilai sebuah fungsi pada nilai yang diamplifikasi.

Tempat yang baik untuk memulai adalah bagaimana kami mengimplementasikan LINQ di C #. Pelajari SelectManymetodenya; itu adalah kunci untuk memahami cara kerja urutan monad di C #. Ini adalah metode yang sangat sederhana, tetapi sangat kuat!


Disarankan, bacaan lebih lanjut:

  1. Untuk penjelasan yang lebih mendalam dan secara teori logis tentang monad dalam bahasa C #, saya sangat merekomendasikan artikel saya ( Eric Lippert ), rekan sejawat, Wes Dyer mengenai masalah ini. Artikel ini menjelaskan monads kepada saya ketika mereka akhirnya "mengklik" untuk saya.
  2. Sebuah ilustrasi yang baik mengapa Anda mungkin ingin monad sekitar (menggunakan Haskell di contoh itu) .
  3. Semacam, "terjemahan" dari artikel sebelumnya ke JavaScript.

Eric Lippert
sumber
17
Ini adalah jawaban yang bagus, tapi kepalaku tidak punya tubuh. Saya akan menindaklanjuti dan menatapnya akhir pekan ini & bertanya kepada Anda jika semuanya tidak beres dan masuk akal di kepala saya.
Paul Nathan
5
Penjelasan yang sangat baik seperti biasa Eric. Untuk diskusi yang lebih teoretis (tapi masih sangat menarik) saya menemukan posting blog Bart De Smet di MinLINQ membantu dalam menghubungkan beberapa konstruksi pemrograman fungsional kembali ke C # juga. community.bartdesmet.net/blogs/bart/archive/2010/01/01/…
Ron Warholic
41
Lebih masuk akal bagi saya untuk mengatakan ini menambah tipe daripada memperkuatnya .
Gabe
61
@slomojo: dan saya mengubahnya kembali ke apa yang saya tulis dan dimaksudkan untuk ditulis. Jika Anda dan Gabe ingin menulis jawaban Anda sendiri, silakan saja.
Eric Lippert
24
@ Eric, Terserah Anda tentu saja, tetapi Amplifier menyiratkan bahwa properti yang ada ditingkatkan, yang menyesatkan.
ocodo
341

Mengapa kita membutuhkan monad?

  1. Kami ingin memprogram hanya menggunakan fungsi . ("pemrograman fungsional" setelah semua -FP).
  2. Lalu, kita punya masalah besar pertama. Ini adalah program:

    f(x) = 2 * x

    g(x,y) = x / y

    Bagaimana kita bisa mengatakan apa yang harus dieksekusi terlebih dahulu ? Bagaimana kita bisa membentuk urutan fungsi yang diurutkan (yaitu program ) menggunakan tidak lebih dari fungsi?

    Solusi: menyusun fungsi . Jika Anda ingin pertama gdan kemudian f, cukup tulis f(g(x,y)). OKE, tapi ...

  3. Lebih banyak masalah: beberapa fungsi mungkin gagal (yaitu g(2,0), bagi dengan 0). Kami tidak memiliki "pengecualian" di FP . Bagaimana kita menyelesaikannya?

    Solusi: Mari kita izinkan fungsi mengembalikan dua jenis hal : alih-alih memiliki g : Real,Real -> Real(fungsi dari dua real menjadi nyata), mari kita izinkan g : Real,Real -> Real | Nothing(fungsi dari dua real menjadi (nyata atau tidak sama sekali)).

  4. Tetapi fungsi seharusnya (lebih sederhana) mengembalikan hanya satu hal .

    Solusi: mari kita buat tipe data baru yang akan dikembalikan, " tipe tinju " yang melingkupi mungkin nyata atau tidak menjadi apa-apa. Karena itu, kita dapat memiliki g : Real,Real -> Maybe Real. OKE, tapi ...

  5. Apa yang terjadi sekarang f(g(x,y))? fbelum siap untuk mengkonsumsi Maybe Real. Dan, kami tidak ingin mengubah setiap fungsi yang kami dapat hubungkan dengan guntuk mengkonsumsi a Maybe Real.

    Solusi: mari kita memiliki fungsi khusus untuk "menghubungkan" / "menulis" / "tautan" fungsi . Dengan begitu, kita dapat, di belakang layar, mengadaptasi output dari satu fungsi untuk memberi makan fungsi berikut.

    Dalam kasus kami: g >>= f(hubungkan / tulis gke f). Kami ingin >>=mendapatkan ghasil, memeriksanya dan, kalau-kalau Nothingtidak menelepon fdan kembali Nothing; atau sebaliknya, ekstrak kotak Realdan beri makan fdengan itu. (Algoritma ini hanya implementasi >>=untuk Maybetipe).

  6. Banyak masalah lain muncul yang dapat dipecahkan dengan menggunakan pola yang sama: 1. Gunakan "kotak" untuk mengkodifikasi / menyimpan makna / nilai yang berbeda, dan memiliki fungsi seperti gitu mengembalikan "nilai kotak" itu. 2. Miliki komposer / penghubung g >>= funtuk membantu menghubungkan gkeluaran ke finput, jadi kami tidak perlu mengubah fsama sekali.

  7. Masalah luar biasa yang dapat dipecahkan dengan menggunakan teknik ini adalah:

    • memiliki keadaan global yang setiap fungsi dalam urutan fungsi ("program") dapat membagikan: solusi StateMonad.

    • Kami tidak suka "fungsi tidak murni": fungsi yang menghasilkan output berbeda untuk input yang sama . Oleh karena itu, mari tandai fungsi-fungsi tersebut, membuatnya untuk mengembalikan nilai yang ditandai / kotak: IOmonad.

Total kebahagiaan !!!!

warga negara1
sumber
2
@DmitriZaitsev Pengecualian hanya dapat terjadi di "kode tidak murni" (IO monad) sejauh yang saya tahu.
cibercitizen1
3
@DmitriZaitsev Peran Tidak ada yang dapat dimainkan oleh tipe lain (berbeda dari Real yang diharapkan). Bukan itu intinya. Dalam contoh, masalahnya adalah bagaimana mengadaptasi fungsi dalam rantai ketika yang sebelumnya dapat mengembalikan tipe nilai yang tidak diharapkan ke yang berikutnya, tanpa merantai yang terakhir (hanya menerima Nyata sebagai input).
cibercitizen1
3
Titik kebingungan lainnya adalah bahwa kata "monad" hanya muncul dua kali dalam jawaban Anda, dan hanya dalam kombinasi dengan istilah-istilah lain - Statedan IO, tanpa satu pun dari mereka serta makna yang tepat dari "monad" yang diberikan
Dmitri Zaitsev
31
Bagi saya sebagai orang yang berasal dari latar belakang OOP jawaban ini benar-benar menjelaskan dengan baik motivasi di balik memiliki monad dan juga apa sebenarnya monad itu (lebih dari jawaban yang diterima). Jadi, saya merasa sangat membantu. Terima kasih banyak @ cibercitizen1 dan +1
akhilless
3
Saya telah membaca tentang pemrograman fungsional selama sekitar satu tahun. Jawaban ini, dan terutama dua poin pertama, akhirnya membuat saya mengerti apa arti pemrograman yang sebenarnya, dan mengapa pemrograman fungsional berbeda. Terima kasih!
jrahhali
82

Saya akan mengatakan analogi OO terdekat dengan monad adalah " pola perintah ".

Dalam pola perintah Anda membungkus pernyataan atau ekspresi biasa dalam objek perintah . Objek perintah mengekspos metode eksekusi yang mengeksekusi pernyataan terbungkus. Jadi pernyataan diubah menjadi objek kelas satu yang dapat diedarkan dan dieksekusi sesuka hati. Perintah dapat dikomposisikan sehingga Anda dapat membuat objek-program dengan merantai dan membuat objek-perintah.

Perintah dijalankan oleh objek terpisah, penyerang . Manfaat menggunakan pola perintah (daripada hanya menjalankan serangkaian pernyataan biasa) adalah bahwa penjajah yang berbeda dapat menerapkan logika yang berbeda dengan bagaimana perintah harus dieksekusi.

Pola perintah dapat digunakan untuk menambah (atau menghapus) fitur bahasa yang tidak didukung oleh bahasa host. Misalnya, dalam bahasa OO hipotetis tanpa pengecualian, Anda bisa menambahkan pengecualian semantik dengan memaparkan metode "coba" dan "buang" ke perintah. Ketika sebuah perintah memanggil lemparan, penyerang mundur melalui daftar (atau pohon) perintah sampai panggilan "coba" terakhir. Sebaliknya, Anda bisa menghapus pengecualian semantik dari bahasa (jika Anda yakin pengecualian itu buruk ) dengan menangkap semua pengecualian yang dilemparkan oleh masing-masing perintah individu, dan mengubahnya menjadi kode kesalahan yang kemudian diteruskan ke perintah berikutnya.

Bahkan semantik eksekusi yang lebih mewah seperti transaksi, eksekusi non-deterministik atau kelanjutan dapat diimplementasikan seperti ini dalam bahasa yang tidak mendukungnya secara asli. Ini adalah pola yang sangat kuat jika Anda memikirkannya.

Sekarang dalam kenyataannya pola perintah tidak digunakan sebagai fitur bahasa umum seperti ini. Overhead mengubah setiap pernyataan menjadi kelas yang terpisah akan menyebabkan jumlah kode boilerplate yang tak tertahankan. Tetapi pada prinsipnya itu dapat digunakan untuk memecahkan masalah yang sama seperti monad digunakan untuk menyelesaikan dalam fp.

JacquesB
sumber
15
Saya percaya ini adalah penjelasan monad pertama yang saya lihat yang tidak bergantung pada konsep pemrograman fungsional dan memasukkannya dalam istilah OOP nyata. Jawaban yang sangat bagus.
David K. Hess
ini sangat dekat dengan apa sebenarnya monad dalam FP / Haskell, kecuali bahwa perintah itu sendiri objek "tahu" yang "logika doa" mereka milik (dan hanya yang kompatibel dapat dirantai bersama); penyerang hanya memasok nilai pertama. Ini tidak seperti perintah "Cetak" yang dapat dieksekusi oleh "logika eksekusi non-deterministik". Tidak, itu harus "I / O logic" (yaitu IO monad). Tapi selain itu, sangat dekat. Anda bahkan bisa mengatakan bahwa Monads hanyalah Program (dibangun dari Pernyataan Kode, untuk Dieksekusi Nanti). Pada masa-masa awal, "bind" disebut sebagai "titik koma yang dapat diprogram" .
Will Ness
1
@ DavidK. Dia benar-benar sangat skeptis terhadap jawaban yang menggunakan FP untuk menjelaskan konsep FP dasar, dan terutama jawaban yang menggunakan bahasa FP seperti Scala. Bagus sekali, JacquesB!
Pasang kembali Monica
62

Dalam hal yang dimengerti oleh programmer OOP (tanpa latar belakang pemrograman fungsional), apa itu monad?

Masalah apa yang dipecahkan dan tempat apa yang paling umum digunakan? Adalah tempat paling umum digunakan?

Dalam hal pemrograman OO, monad adalah antarmuka (atau lebih mungkin mixin), diparameterisasi berdasarkan tipe, dengan dua metode, returndan bindyang menggambarkan:

  • Cara menyuntikkan nilai untuk mendapatkan nilai monadik dari tipe nilai yang disuntikkan itu;
  • Cara menggunakan fungsi yang membuat nilai monadik dari yang non-monadik, pada nilai monadik.

Masalah yang dipecahkannya adalah jenis masalah yang sama dengan yang Anda harapkan dari antarmuka apa pun, yaitu, "Saya memiliki banyak kelas berbeda yang melakukan hal yang berbeda, tetapi tampaknya melakukan hal-hal yang berbeda dengan cara yang memiliki kesamaan mendasar. dapatkah saya menggambarkan kesamaan di antara mereka, bahkan jika kelas itu sendiri tidak benar-benar subtipe dari sesuatu yang lebih dekat daripada kelas 'Objek' itu sendiri? "

Lebih khusus lagi, Monad"antarmuka" mirip dengan IEnumeratoratau IIteratordalam hal itu mengambil jenis yang itu sendiri mengambil jenis. "Titik" utama Monadadalah mampu menghubungkan operasi berdasarkan tipe interior, bahkan ke titik memiliki "tipe internal" baru, sambil menjaga - atau bahkan meningkatkan - struktur informasi kelas utama.

BMeph
sumber
1
returntidak akan benar-benar menjadi metode pada monad, karena itu tidak mengambil contoh monad sebagai argumen. (yaitu: tidak ada ini / diri)
Laurence Gonsalves
@LaurenceGonsalves: Karena saya sedang melihat ini untuk tesis sarjana saya, saya pikir apa yang paling membatasi adalah kurangnya metode statis pada antarmuka di C # / Java. Anda bisa mendapatkan jauh ke arah penerapan seluruh cerita monad, setidaknya terikat secara statis alih-alih berdasarkan pada kacamata ketik. Menariknya, ini bahkan akan berhasil meskipun kurangnya jenis yang lebih tinggi.
Sebastian Graf
42

Anda memiliki presentasi baru-baru ini " Monadologie - bantuan profesional tentang kecemasan tipe " oleh Christopher League (12 Juli 2010), yang cukup menarik pada topik kelanjutan dan monad.
Video yang disertai presentasi (slideshare) ini sebenarnya tersedia di vimeo .
Bagian Monad dimulai sekitar 37 menit dalam, pada video satu jam ini, dan dimulai dengan slide 42 dari 58 presentasi slide.

Ini disajikan sebagai "pola desain terkemuka untuk pemrograman fungsional", tetapi bahasa yang digunakan dalam contoh adalah Scala, yang merupakan OOP dan fungsional.
Anda dapat membaca lebih lanjut tentang Monad di Scala di posting blog " Monads - Cara lain untuk menghitung abstrak di Scala ", dari Debasish Ghosh (27 Maret 2008).

Tipe konstruktor M adalah monad jika mendukung operasi ini:

# the return function
def unit[A] (x: A): M[A]

# called "bind" in Haskell 
def flatMap[A,B] (m: M[A]) (f: A => M[B]): M[B]

# Other two can be written in term of the first two:

def map[A,B] (m: M[A]) (f: A => B): M[B] =
  flatMap(m){ x => unit(f(x)) }

def andThen[A,B] (ma: M[A]) (mb: M[B]): M[B] =
  flatMap(ma){ x => mb }

Jadi misalnya (dalam Scala):

  • Option adalah monad
    unit def [A] (x: A): Opsi [A] = Beberapa (x)

    def flatMap [A, B] (m: Opsi [A]) (f: A => Opsi [B]): Opsi [B] =
      m cocok {
       case None => None
       case Beberapa (x) => f (x)
      }
  • List adalah Monad
    unit def [A] (x: A): Daftar [A] = Daftar (x)

    def flatMap [A, B] (m: Daftar [A]) (f: A => Daftar [B]): Daftar [B] =
      m cocok {
        case Nil => Nil
        case x :: xs => f (x) ::: flatMap (xs) (f)
      }

Monad adalah masalah besar di Scala karena sintaksis yang mudah dibangun untuk memanfaatkan struktur Monad:

forpemahaman dalam Scala :

for {
  i <- 1 to 4
  j <- 1 to i
  k <- 1 to j
} yield i*j*k

diterjemahkan oleh kompiler ke:

(1 to 4).flatMap { i =>
  (1 to i).flatMap { j =>
    (1 to j).map { k =>
      i*j*k }}}

Kunci abstraksi adalah flatMap, yang mengikat perhitungan melalui rantai.
Setiap doa flatMapmengembalikan tipe struktur data yang sama (tapi nilainya berbeda), yang berfungsi sebagai input ke perintah berikutnya dalam rantai.

Dalam cuplikan di atas, flatMap mengambil sebagai input penutupan (SomeType) => List[AnotherType]dan mengembalikan a List[AnotherType]. Poin penting yang perlu diperhatikan adalah bahwa semua flatMaps mengambil tipe penutupan yang sama dengan input dan mengembalikan tipe yang sama dengan output.

Inilah yang "mengikat" utas perhitungan - setiap item dari urutan dalam untuk pemahaman harus menghormati batasan jenis yang sama ini.


Jika Anda mengambil dua operasi (yang mungkin gagal) dan meneruskan hasilnya ke yang ketiga, seperti:

lookupVenue: String => Option[Venue]
getLoggedInUser: SessionID => Option[User]
reserveTable: (Venue, User) => Option[ConfNo]

tetapi tanpa memanfaatkan Monad, Anda mendapatkan kode OOP yang berbelit-belit seperti:

val user = getLoggedInUser(session)
val confirm =
  if(!user.isDefined) None
  else lookupVenue(name) match {
    case None => None
    case Some(venue) =>
      val confno = reserveTable(venue, user.get)
      if(confno.isDefined)
        mailTo(confno.get, user.get)
      confno
  }

sedangkan dengan Monad, Anda dapat bekerja dengan tipe aktual ( Venue, User) seperti semua operasi, dan merahasiakan opsi verifikasi Opsi, semuanya karena flatmap dari sintaks untuk:

val confirm = for {
  venue <- lookupVenue(name)
  user <- getLoggedInUser(session)
  confno <- reserveTable(venue, user)
} yield {
  mailTo(confno, user)
  confno
}

Bagian hasil hanya akan dieksekusi jika ketiga fungsi memiliki Some[X]; semua Noneakan langsung dikembalikan ke confirm.


Begitu:

Monads memungkinkan perhitungan yang diperintahkan dalam Fungsional Programing, yang memungkinkan kita untuk memodelkan urutan tindakan dalam bentuk terstruktur yang bagus, agak seperti DSL.

Dan kekuatan terbesar datang dengan kemampuan untuk menyusun monad yang melayani tujuan yang berbeda, menjadi abstraksi yang dapat diperluas dalam suatu aplikasi.

Urutan dan threading tindakan oleh monad ini dilakukan oleh kompiler bahasa yang melakukan transformasi melalui keajaiban penutupan.


Omong-omong, Monad bukan hanya model perhitungan yang digunakan dalam FP:

Teori kategori mengusulkan banyak model perhitungan. Diantara mereka

  • model panah dari perhitungan
  • model perhitungan Monad
  • model perhitungan yang berlaku
VONC
sumber
2
Saya suka penjelasan ini! Contoh yang Anda berikan menunjukkan konsep dengan indah dan juga menambahkan apa yang IMHO hilang dari penggoda Eric tentang SelectMany () menjadi Monad. Terima kasih untuk ini!
aoven
1
IMHO ini adalah jawaban yang paling elegan
Polymerase
dan sebelum yang lainnya, Functor.
Will Ness
34

Untuk menghormati pembaca cepat, saya mulai dengan definisi yang tepat terlebih dahulu, lanjutkan dengan penjelasan yang lebih cepat "bahasa Inggris polos", dan kemudian pindah ke contoh.

Berikut adalah definisi ringkas dan tepat yang sedikit ditulis ulang:

Sebuah monad (dalam ilmu komputer) secara resmi peta yang:

  • mengirimkan setiap jenis Xbeberapa bahasa pemrograman yang diberikan ke tipe baru T(X)(disebut "tipe T-komputasi dengan nilai dalam X");

  • dilengkapi dengan aturan untuk menyusun dua fungsi formulir f:X->T(Y)dan g:Y->T(Z)fungsi g∘f:X->T(Z);

  • dengan cara yang asosiatif dalam arti yang jelas dan unital sehubungan dengan fungsi unit yang disebut pure_X:X->T(X), untuk dianggap sebagai mengambil nilai ke perhitungan murni yang hanya mengembalikan nilai itu.

Jadi dengan kata sederhana, monad adalah aturan untuk berpindah dari tipe apa pun Xke tipe lainT(X) , dan aturan untuk berpindah dari dua fungsi f:X->T(Y)dan g:Y->T(Z)(yang ingin Anda buat tetapi tidak bisa) ke fungsi baruh:X->T(Z) . Namun, yang bukan komposisi dalam arti matematika yang ketat. Kami pada dasarnya adalah "menekuk" komposisi fungsi atau mendefinisikan kembali bagaimana fungsi disusun.

Plus, kita membutuhkan aturan monad untuk menyusun untuk memenuhi aksioma matematika "jelas":

  • Asosiatif : Menulis fdengan gdan kemudian dengan h(dari luar) harus sama dengan menulis gdengan hdan kemudian dengan f(dari dalam).
  • Properti Unital : Menyusun fdengan fungsi identitas di kedua sisi harus menghasilkan f.

Sekali lagi, dengan kata-kata sederhana, kita tidak bisa menjadi gila mendefinisikan ulang komposisi fungsi kita seperti yang kita inginkan:

  • Pertama-tama kita perlu asosiatif untuk dapat menyusun beberapa fungsi dalam satu baris f(g(h(k(x))), misalnya , dan tidak perlu khawatir menentukan urutan yang menyusun pasangan fungsi. Karena aturan monad hanya mengatur cara membuat sepasang fungsi , tanpa aksioma itu, kita perlu mengetahui pasangan mana yang pertama kali disusun dan seterusnya. (Catatan yang berbeda dari properti komutatif yang fdikomposisi gsama dengan yang gdikomposisikan f, yang tidak diperlukan).
  • Dan kedua, kita membutuhkan properti unital, yang hanya mengatakan bahwa identitas membentuk sepele seperti yang kita harapkan. Jadi kita dapat dengan aman memperbarui fungsi kapan pun identitas itu dapat diekstraksi.

Jadi sekali lagi secara singkat: Monad adalah aturan ekstensi tipe dan fungsi penulisan yang memuaskan dua aksioma - asosiasi dan properti unital.

Dalam istilah praktis, Anda ingin monad diimplementasikan untuk Anda oleh bahasa, kompiler atau kerangka kerja yang akan mengatur fungsi penulisan untuk Anda. Jadi Anda bisa fokus menulis logika fungsi Anda daripada mengkhawatirkan bagaimana eksekusi mereka diimplementasikan.

Singkatnya, singkatnya.


Menjadi ahli matematika profesional, saya lebih suka menghindari menyebut h"komposisi" dari fdan g. Karena secara matematis, tidak. Menyebutnya "komposisi" salah mengira itu hadalah komposisi matematika yang benar, yang tidak. Bahkan tidak ditentukan secara unik oleh fdan g. Alih-alih, itu adalah hasil dari "aturan komposisi" fungsi baru monad kami. Yang bisa sangat berbeda dari komposisi matematika yang sebenarnya bahkan jika yang terakhir ada!


Agar kurang kering, izinkan saya mencoba mengilustrasikannya dengan contoh bahwa saya membuat anotasi dengan bagian-bagian kecil, sehingga Anda dapat langsung beralih ke intinya.

Melontarkan pengecualian sebagai contoh Monad

Misalkan kita ingin membuat dua fungsi:

f: x -> 1 / x
g: y -> 2 * y

Tetapi f(0)tidak didefinisikan, jadi pengecualian edilemparkan. Lalu bagaimana Anda bisa mendefinisikan nilai komposisi g(f(0))? Lempar pengecualian lagi, tentu saja! Mungkin sama e. Mungkin pengecualian baru yang diperbarui e1.

Apa tepatnya yang terjadi di sini? Pertama, kita membutuhkan nilai pengecualian baru (berbeda atau sama). Anda dapat memanggil mereka nothingatau nullatau apa pun tetapi esensinya tetap sama - mereka harus berupa nilai baru, misalnya tidak boleh numberdalam contoh kita di sini. Saya lebih suka untuk tidak memanggil mereka nulluntuk menghindari kebingungan dengan bagaimana nulldapat diimplementasikan dalam bahasa tertentu. Saya juga lebih suka menghindari nothingkarena sering dikaitkan dengan null, yang, pada prinsipnya, adalah apa yang nullharus dilakukan, prinsip itu sering ditekuk karena alasan praktis apa pun.

Apa sebenarnya pengecualian itu?

Ini adalah masalah sepele untuk setiap programmer berpengalaman tetapi saya ingin memberikan beberapa kata hanya untuk memadamkan worm kebingungan:

Pengecualian adalah objek yang merangkum informasi tentang bagaimana hasil eksekusi yang tidak valid terjadi.

Ini dapat berkisar dari membuang semua detail dan mengembalikan nilai global tunggal (seperti NaNatau null) atau membuat daftar log panjang atau apa yang sebenarnya terjadi, mengirimkannya ke database dan mereplikasi seluruh lapisan penyimpanan data terdistribusi;)

Perbedaan penting antara dua contoh pengecualian ekstrim ini adalah bahwa dalam kasus pertama tidak ada efek samping . Yang kedua ada. Yang membawa kita pada pertanyaan (ribuan dolar):

Apakah pengecualian diperbolehkan dalam fungsi murni?

Jawaban yang lebih singkat : Ya, tetapi hanya ketika mereka tidak mengarah ke efek samping.

Jawaban yang lebih panjang. Agar murni, output fungsi Anda harus ditentukan secara unik oleh inputnya. Jadi kami mengubah fungsi kami fdengan mengirimkan 0ke nilai abstrak baru eyang kami sebut pengecualian. Kami memastikan bahwa nilai etidak mengandung informasi luar yang tidak ditentukan secara unik oleh input kami, yaitu x. Jadi di sini adalah contoh pengecualian tanpa efek samping:

e = {
  type: error, 
  message: 'I got error trying to divide 1 by 0'
}

Dan inilah efek sampingnya:

e = {
  type: error, 
  message: 'Our committee to decide what is 1/0 is currently away'
}

Sebenarnya, itu hanya memiliki efek samping jika pesan itu dapat berubah di masa depan. Tetapi jika dijamin tidak akan pernah berubah, nilai itu menjadi dapat diprediksi secara unik, sehingga tidak ada efek samping.

Untuk membuatnya lebih konyol. Suatu fungsi yang 42pernah kembali jelas murni. Tetapi jika seseorang gila memutuskan untuk membuat 42variabel yang nilainya mungkin berubah, fungsi yang sama berhenti menjadi murni di bawah kondisi baru.

Perhatikan bahwa saya menggunakan objek notasi literal untuk kesederhanaan untuk menunjukkan esensi. Sayangnya hal-hal kacau dalam bahasa seperti JavaScript, di mana errorbukan jenis yang berperilaku seperti yang kita inginkan di sini sehubungan dengan komposisi fungsi, sedangkan jenis yang sebenarnya suka nullatau NaNtidak berperilaku seperti ini melainkan melalui beberapa buatan dan tidak selalu intuitif ketik konversi.

Ketik ekstensi

Karena kami ingin memvariasikan pesan di dalam pengecualian kami, kami benar-benar mendeklarasikan tipe baru Euntuk keseluruhan objek pengecualian dan kemudian Itulah yang maybe numberdilakukannya, selain dari namanya yang membingungkan, yang bisa berupa tipe numberatau tipe pengecualian baru E, sehingga benar-benar serikat number | Edari numberdan E. Secara khusus, itu tergantung pada bagaimana kita ingin membangun E, yang tidak disarankan atau tercermin dalam namanya maybe number.

Apa itu komposisi fungsional?

Ini adalah operasi matematika mengambil fungsi f: X -> Ydan g: Y -> Zdan membangun komposisi mereka sebagai fungsi yang h: X -> Zmemuaskan h(x) = g(f(x)). Masalah dengan definisi ini terjadi ketika hasilnya f(x)tidak diizinkan sebagai argumen g.

Dalam matematika, fungsi-fungsi itu tidak dapat disusun tanpa kerja ekstra. Solusi matematis ketat untuk contoh di atas fdan gadalah untuk menghapus 0dari set definisi f. Dengan seperangkat definisi baru (tipe baru yang lebih ketat x), dapat fdikomposisikan dengan g.

Namun, sangat tidak praktis dalam pemrograman untuk membatasi sekumpulan definisi fseperti itu. Sebagai gantinya, pengecualian dapat digunakan.

Atau sebagai pendekatan lain, nilai-nilai buatan yang dibuat seperti NaN, undefined, null, Infinitydll Jadi Anda mengevaluasi 1/0untuk Infinitydan 1/-0untuk -Infinity. Dan kemudian paksa nilai baru kembali ke ekspresi Anda alih-alih melemparkan pengecualian. Mengarah ke hasil yang Anda mungkin atau mungkin tidak dapat diprediksi:

1/0                // => Infinity
parseInt(Infinity) // => NaN
NaN < 0            // => false
false + 1          // => 1

Dan kami kembali ke nomor reguler yang siap untuk pindah;)

JavaScript memungkinkan kami untuk terus mengeksekusi ekspresi numerik dengan biaya berapa pun tanpa menimbulkan kesalahan seperti pada contoh di atas. Itu berarti, itu juga memungkinkan untuk menulis fungsi. Yang persis tentang apa itu monad - itu adalah aturan untuk menyusun fungsi yang memuaskan aksioma seperti yang didefinisikan pada awal jawaban ini.

Tetapi apakah aturan penyusunan fungsi, yang timbul dari implementasi JavaScript untuk menangani kesalahan numerik, monad?

Untuk menjawab pertanyaan ini, yang Anda butuhkan adalah memeriksa aksioma (dibiarkan sebagai latihan bukan bagian dari pertanyaan di sini;).

Bisakah melempar pengecualian digunakan untuk membangun monad?

Memang, monad yang lebih bermanfaat sebagai gantinya adalah aturan yang menetapkan bahwa jika fmelempar pengecualian untuk beberapa orang x, demikian juga komposisinya dengan apapun g. Plus membuat pengecualian Esecara global unik dengan hanya satu nilai yang mungkin ( objek terminal dalam teori kategori). Sekarang kedua aksioma dapat langsung diperiksa dan kita mendapatkan monad yang sangat berguna. Dan hasilnya adalah apa yang dikenal sebagai monad mungkin .

Dmitri Zaitsev
sumber
3
Kontribusi yang bagus. +1 Tetapi mungkin Anda ingin menghapus "telah menemukan sebagian besar penjelasan terlalu lama ..." menjadi milik Anda yang paling lama. Orang lain akan menilai apakah itu "bahasa Inggris biasa" seperti yang diminta pertanyaan: "bahasa Inggris sederhana == dengan kata-kata sederhana, dengan cara yang sederhana".
cibercitizen1
@ cibercitizen1 Terima kasih! Ini sebenarnya singkat, jika Anda tidak menghitung contohnya. Poin utamanya adalah Anda tidak perlu membaca contoh untuk memahami definisi . Sayangnya banyak penjelasan memaksa saya untuk membaca contoh terlebih dahulu , yang seringkali tidak perlu tetapi, tentu saja, mungkin memerlukan kerja ekstra untuk penulis. Dengan terlalu bergantung pada contoh-contoh spesifik, ada bahaya bahwa detail yang tidak penting mengaburkan gambar dan membuatnya lebih sulit untuk dipahami. Karena itu, Anda memiliki poin yang valid, lihat pembaruan.
Dmitri Zaitsev
2
terlalu lama dan membingungkan
seenimurugan
1
@seenimurugan Saran perbaikan dipersilakan;)
Dmitri Zaitsev
26

Monad adalah tipe data yang merangkum nilai, dan yang pada dasarnya, dua operasi dapat diterapkan:

  • return x menciptakan nilai tipe monad yang merangkum x
  • m >>= f(membacanya sebagai "operator mengikat") menerapkan fungsi fke nilai di monadm

Itulah monad itu. Ada beberapa teknis lagi , tetapi pada dasarnya kedua operasi tersebut mendefinisikan sebuah monad. Pertanyaan sebenarnya adalah, "Apa yang dilakukan monad ?", Dan itu tergantung pada daftar - monad adalah monad, Maybes adalah monad, operasi IO adalah monad. Semua itu artinya ketika kita mengatakan hal-hal itu adalah monad adalah bahwa mereka memiliki antarmuka monad returndan >>=.

Membuang
sumber
“Apa yang dilakukan monad, dan itu tergantung pada monad”: dan lebih tepatnya, itu tergantung pada bindfungsi yang harus didefinisikan untuk setiap tipe monadik, bukan? Itu akan menjadi alasan yang baik untuk tidak membingungkan ikatan dengan komposisi, karena ada definisi tunggal untuk komposisi, sementara tidak ada hanya definisi tunggal untuk fungsi mengikat, ada satu per tipe monadik, jika saya mengerti dengan benar.
Hibou57
14

Dari wikipedia :

Dalam pemrograman fungsional, monad adalah sejenis tipe data abstrak yang digunakan untuk mewakili perhitungan (bukan data dalam model domain). Monads memungkinkan pemrogram untuk mengaitkan tindakan bersama-sama untuk membangun saluran pipa, di mana setiap tindakan dihiasi dengan aturan pemrosesan tambahan yang disediakan oleh monad. Program yang ditulis dalam gaya fungsional dapat menggunakan monad untuk menyusun prosedur yang mencakup operasi berurutan, 1 [2] atau untuk menentukan aliran kontrol sewenang-wenang (seperti menangani konkurensi, kelanjutan, atau pengecualian).

Secara formal, sebuah monad dibangun dengan mendefinisikan dua operasi (bind dan return) dan konstruktor tipe M yang harus memenuhi beberapa properti untuk memungkinkan komposisi fungsi monadik yang benar (yaitu fungsi yang menggunakan nilai dari monad sebagai argumennya). Operasi pengembalian mengambil nilai dari tipe polos dan memasukkannya ke dalam wadah monadik tipe M. Operasi mengikat melakukan proses sebaliknya, mengekstraksi nilai asli dari wadah dan meneruskannya ke fungsi berikutnya yang terkait di dalam pipa.

Seorang programmer akan menyusun fungsi-fungsi monadik untuk mendefinisikan pipa pemrosesan data. Monad bertindak sebagai kerangka kerja, karena merupakan perilaku yang dapat digunakan kembali yang menentukan urutan fungsi monadik tertentu dalam pipa disebut, dan mengelola semua pekerjaan yang disembunyikan yang diperlukan oleh perhitungan. [3] Operator bind dan return yang disisipkan dalam pipeline akan dieksekusi setelah setiap fungsi monadik mengembalikan kontrol, dan akan menangani aspek-aspek tertentu yang ditangani oleh monad.

Saya percaya ini menjelaskan dengan sangat baik.

the_drow
sumber
12

Saya akan mencoba membuat definisi terpendek yang dapat saya kelola dengan menggunakan istilah OOP:

Kelas generik CMonadic<T>adalah monad jika mendefinisikan setidaknya metode berikut:

class CMonadic<T> { 
    static CMonadic<T> create(T t);  // a.k.a., "return" in Haskell
    public CMonadic<U> flatMap<U>(Func<T, CMonadic<U>> f); // a.k.a. "bind" in Haskell
}

dan jika undang-undang berikut berlaku untuk semua jenis T dan nilainya yang mungkin t

identitas kiri:

CMonadic<T>.create(t).flatMap(f) == f(t)

identitas yang benar

instance.flatMap(CMonadic<T>.create) == instance

asosiatif:

instance.flatMap(f).flatMap(g) == instance.flatMap(t => f(t).flatMap(g))

Contoh :

Daftar monad mungkin memiliki:

List<int>.create(1) --> [1]

Dan flatMap pada daftar [1,2,3] dapat berfungsi seperti:

intList.flatMap(x => List<int>.makeFromTwoItems(x, x*10)) --> [1,10,2,20,3,30]

Iterables dan Observable juga dapat dibuat monadik, serta Janji dan Tugas.

Komentar :

Monad tidak serumit itu. The flatMapFungsi adalah banyak seperti yang lebih umum ditemui map. Ini menerima argumen fungsi (juga dikenal sebagai delegasi), yang dapat dipanggil (segera atau lambat, nol atau lebih kali) dengan nilai yang berasal dari kelas generik. Ia mengharapkan bahwa fungsi yang lewat juga membungkus nilai kembalinya dalam jenis generik yang sama. Untuk membantu itu, ia menyediakan create, sebuah konstruktor yang dapat membuat turunan dari kelas generik dari suatu nilai. Hasil kembalinya flatMap juga merupakan kelas generik dari jenis yang sama, seringkali mengemas nilai yang sama yang terkandung dalam hasil kembalinya satu atau lebih aplikasi flatMap ke nilai yang sebelumnya terkandung. Ini memungkinkan Anda untuk membuat rantai flatMap sebanyak yang Anda inginkan:

intList.flatMap(x => List<int>.makeFromTwo(x, x*10))
       .flatMap(x => x % 3 == 0 
                   ? List<string>.create("x = " + x.toString()) 
                   : List<string>.empty())

Kebetulan kelas generik semacam ini berguna sebagai model dasar untuk banyak hal. Ini (bersama dengan teori kategori jargonisme) adalah alasan mengapa Monads tampak begitu sulit untuk dipahami atau dijelaskan. Mereka adalah hal yang sangat abstrak dan hanya menjadi jelas berguna setelah mereka terspesialisasi.

Misalnya, Anda bisa memodelkan pengecualian menggunakan wadah monadik. Setiap kontainer akan berisi hasil operasi atau kesalahan yang telah terjadi. Fungsi berikutnya (mendelegasikan) dalam rantai panggilan balik flatMap hanya akan dipanggil jika yang sebelumnya mengemas nilai dalam wadah. Kalau tidak, jika kesalahan telah dikemas, kesalahan akan terus menyebar melalui wadah berantai sampai ditemukan wadah yang memiliki fungsi penangan kesalahan yang dilampirkan melalui metode yang disebut .orElse()(metode seperti itu akan menjadi ekstensi yang diizinkan)

Catatan : Bahasa fungsional memungkinkan Anda untuk menulis fungsi yang dapat beroperasi pada semua jenis kelas generik monadik. Agar ini berfungsi, orang harus menulis antarmuka generik untuk monad. Saya tidak tahu apakah mungkin untuk menulis antarmuka seperti itu di C #, tetapi sejauh yang saya tahu tidak:

interface IMonad<T> { 
    static IMonad<T> create(T t); // not allowed
    public IMonad<U> flatMap<U>(Func<T, IMonad<U>> f); // not specific enough,
    // because the function must return the same kind of monad, not just any monad
}
rev. Gorgi Kosev
sumber
7

Apakah monad memiliki interpretasi "alami" dalam OO tergantung pada monad. Dalam bahasa seperti Java, Anda dapat menerjemahkan monad mungkin ke bahasa memeriksa pointer nol, sehingga perhitungan yang gagal (yaitu, menghasilkan Tidak ada di Haskell) memancarkan null pointer sebagai hasilnya. Anda dapat menerjemahkan negara monad ke dalam bahasa yang dihasilkan dengan membuat variabel yang dapat berubah dan metode untuk mengubah keadaannya.

Monad adalah monoid dalam kategori endofunctors.

Informasi yang dikumpulkan oleh kalimat itu sangat dalam. Dan Anda bekerja di monad dengan bahasa imperatif apa pun. Monad adalah bahasa khusus domain "berurutan". Ini memenuhi sifat menarik tertentu, yang diambil bersama-sama membuat monad model matematika "pemrograman imperatif". Haskell memudahkan untuk mendefinisikan bahasa imperatif kecil (atau besar), yang dapat digabungkan dalam berbagai cara.

Sebagai seorang programmer OO, Anda menggunakan hierarki kelas bahasa Anda untuk mengatur jenis fungsi atau prosedur yang dapat dipanggil dalam konteks, apa yang Anda sebut objek. Monad juga merupakan abstraksi ide ini, sejauh monad yang berbeda dapat digabungkan dengan cara yang sewenang-wenang, secara efektif "mengimpor" semua metode sub-monad ke dalam ruang lingkup.

Secara arsitektural, seseorang kemudian menggunakan tipe tanda tangan untuk secara eksplisit menyatakan konteks mana yang dapat digunakan untuk menghitung nilai.

Seseorang dapat menggunakan trafo monad untuk tujuan ini, dan ada koleksi berkualitas tinggi dari semua monad "standar":

  • Daftar (perhitungan non-deterministik, dengan memperlakukan daftar sebagai domain)
  • Mungkin (perhitungan yang bisa gagal, tetapi pelaporannya tidak penting)
  • Kesalahan (perhitungan yang dapat gagal dan membutuhkan penanganan pengecualian)
  • Pembaca (perhitungan yang dapat diwakili oleh komposisi fungsi Haskell biasa)
  • Penulis (perhitungan dengan "rendering" / "logging" berurutan (untuk string, html dll)
  • Cont (lanjutan)
  • IO (perhitungan yang tergantung pada sistem komputer yang mendasarinya)
  • Negara (perhitungan yang konteksnya mengandung nilai yang dapat dimodifikasi)

dengan transformer monad yang sesuai dan kelas tipe. Kelas tipe memungkinkan pendekatan komplementer untuk menggabungkan monad dengan menyatukan antarmuka mereka, sehingga monad konkret dapat mengimplementasikan antarmuka standar untuk "jenis" monad. Sebagai contoh, module Control.Monad.State berisi kelas MonadState sm, dan (State s) adalah turunan dari form

instance MonadState s (State s) where
    put = ...
    get = ...

Ceritanya yang panjang adalah bahwa monad adalah fungsi yang melampirkan "konteks" ke suatu nilai, yang memiliki cara untuk menyuntikkan nilai ke dalam monad, dan yang memiliki cara untuk mengevaluasi nilai sehubungan dengan konteks yang melekat padanya, setidaknya secara terbatas.

Begitu:

return :: a -> m a

adalah fungsi yang menyuntikkan nilai tipe a ke "aksi" monad tipe m a.

(>>=) :: m a -> (a -> m b) -> m b

adalah fungsi yang mengambil tindakan monad, mengevaluasi hasilnya, dan menerapkan fungsi pada hasilnya. Yang rapi tentang (>> =) adalah hasilnya di monad yang sama. Dengan kata lain, dalam m >> = f, (>> =) menarik hasilnya dari m, dan mengikatnya ke f, sehingga hasilnya ada di monad. (Atau, kita dapat mengatakan bahwa (>> =) menarik f ke m dan menerapkannya pada hasilnya.) Sebagai konsekuensinya, jika kita memiliki f :: a -> mb, dan g :: b -> mc, kita dapat "urutan" tindakan:

m >>= f >>= g

Atau, menggunakan "do notation"

do x <- m
   y <- f x
   g y

Jenis untuk (>>) mungkin menerangi. ini

(>>) :: m a -> m b -> m b

Ini sesuai dengan operator (;) dalam bahasa prosedural seperti C. Memungkinkan notasi seperti:

m = do x <- someQuery
       someAction x
       theNextAction
       andSoOn

Dalam logika matematika dan filosofis, kita memiliki bingkai dan model, yang "secara alami" dimodelkan dengan monadisme. Interpretasi adalah fungsi yang melihat ke dalam domain model dan menghitung nilai kebenaran (atau generalisasi) dari proposisi (atau formula, di bawah generalisasi). Dalam logika modal untuk keperluan, kita dapat mengatakan bahwa proposisi diperlukan jika itu benar dalam "setiap dunia yang mungkin" - jika itu benar sehubungan dengan setiap domain yang dapat diterima. Ini berarti bahwa model dalam bahasa untuk suatu proposisi dapat diverifikasi sebagai model yang domainnya terdiri dari kumpulan model yang berbeda (satu yang sesuai dengan setiap dunia yang mungkin). Setiap monad memiliki metode bernama "join" yang meratakan lapisan, yang menyiratkan bahwa setiap tindakan monad yang hasilnya adalah tindakan monad dapat tertanam dalam monad.

join :: m (m a) -> m a

Lebih penting lagi, itu berarti bahwa monad ditutup di bawah operasi "lapisan susun". Inilah cara kerja transformer monad: mereka menggabungkan monad dengan menyediakan metode "bergabung-seperti" untuk tipe-tipe seperti

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }

sehingga kita bisa mengubah aksi dalam (MaybeT m) menjadi aksi dalam m, secara efektif menghancurkan lapisan. Dalam hal ini, runMaybeT :: MaybeT ma -> m (Mungkin a) adalah metode join-like kami. (MaybeT m) adalah monad, dan MaybeT :: m (Maybe a) -> MaybeT ma secara efektif merupakan konstruktor untuk tipe baru aksi monad di m.

Monad gratis untuk functor adalah monad yang dihasilkan oleh penumpukan f, dengan implikasi bahwa setiap urutan konstruktor untuk f adalah elemen dari monad bebas (atau, lebih tepatnya, sesuatu dengan bentuk yang sama dengan pohon urutan konstruktor untuk f). Monad gratis adalah teknik yang berguna untuk membangun monad fleksibel dengan jumlah pelat ketel yang minimal. Dalam program Haskell, saya dapat menggunakan monad gratis untuk mendefinisikan monad sederhana untuk "pemrograman sistem tingkat tinggi" untuk membantu menjaga keamanan tipe (Saya hanya menggunakan tipe dan deklarasi mereka. Implementasinya sangat mudah dengan penggunaan kombinator):

data RandomF r a = GetRandom (r -> a) deriving Functor
type Random r a = Free (RandomF r) a


type RandomT m a = Random (m a) (m a) -- model randomness in a monad by computing random monad elements.
getRandom     :: Random r r
runRandomIO   :: Random r a -> IO a (use some kind of IO-based backend to run)
runRandomIO'  :: Random r a -> IO a (use some other kind of IO-based backend)
runRandomList :: Random r a -> [a]  (some kind of list-based backend (for pseudo-randoms))

Monadisme adalah arsitektur yang mendasari apa yang Anda sebut pola "interpreter" atau "command", disarikan ke bentuk yang paling jelas, karena setiap perhitungan monadik harus "dijalankan", setidaknya sepele. (Sistem runtime menjalankan monad IO untuk kita, dan merupakan titik masuk ke program Haskell. IO "menggerakkan" sisa perhitungan, dengan menjalankan tindakan IO secara berurutan).

Jenis untuk bergabung juga di mana kita mendapatkan pernyataan bahwa monad adalah monoid dalam kategori endofunctors. Gabung biasanya lebih penting untuk tujuan teoritis, berdasarkan jenisnya. Tetapi memahami tipenya berarti memahami monad. Tipe join-like dan join monad transformer adalah komposisi endofunctor yang efektif, dalam arti komposisi fungsi. Untuk memasukkannya ke dalam bahasa pseudo seperti Haskell,

Foo :: m (ma) <-> (m. M) a

nomen
sumber
3

Monad adalah array fungsi

(PST: berbagai fungsi hanyalah perhitungan).

Sebenarnya, alih-alih array yang benar (satu fungsi dalam satu array sel) Anda memiliki fungsi-fungsi tersebut dirantai oleh fungsi lain >> =. >> = memungkinkan untuk mengadaptasi hasil dari fungsi i untuk memberi makan fungsi i + 1, melakukan perhitungan di antara mereka atau, bahkan, untuk tidak memanggil fungsi i +1.

Jenis yang digunakan di sini adalah "jenis dengan konteks". Ini adalah nilai dengan "tag". Fungsi yang dirantai harus mengambil "nilai telanjang" dan mengembalikan hasil yang ditandai. Salah satu tugas >> = adalah mengekstraksi nilai telanjang di luar konteksnya. Ada juga fungsi "kembali", yang mengambil nilai telanjang dan meletakkannya dengan tag.

Contoh dengan Maybe . Mari kita gunakan untuk menyimpan bilangan bulat sederhana tempat membuat perhitungan.

-- a * b
multiply :: Int -> Int -> Maybe Int
multiply a b = return  (a*b)

-- divideBy 5 100 = 100 / 5
divideBy :: Int -> Int -> Maybe Int
divideBy 0 _ = Nothing -- dividing by 0 gives NOTHING
divideBy denom num = return (quot num denom) -- quotient of num / denom

-- tagged value
val1 = Just 160 

-- array of functions feeded with val1
array1 = val1 >>= divideBy 2  >>= multiply 3 >>= divideBy  4 >>= multiply 3

-- array of funcionts created with the do notation
-- equals array1 but for the feeded val1
array2 :: Int -> Maybe Int
array2 n = do
       v <- divideBy 2  n
       v <- multiply 3 v
       v <- divideBy 4 v
       v <- multiply 3 v
       return v

-- array of functions, 
-- the first >>= performs 160 / 0, returning Nothing
-- the second >>= has to perform Nothing >>= multiply 3 ....
-- and simply returns Nothing without calling multiply 3 ....
array3 = val1 >>= divideBy 0  >>= multiply 3 >>= divideBy  4 >>= multiply 3

main = do
     print array1
     print (array2 160)
     print array3

Hanya untuk menunjukkan bahwa monad adalah array fungsi dengan operasi pembantu, pertimbangkan yang setara dengan contoh di atas, hanya menggunakan array fungsi yang nyata

type MyMonad = [Int -> Maybe Int] -- my monad as a real array of functions

myArray1 = [divideBy 2, multiply 3, divideBy 4, multiply 3]

-- function for the machinery of executing each function i with the result provided by function i-1
runMyMonad :: Maybe Int -> MyMonad -> Maybe Int
runMyMonad val [] = val
runMyMonad Nothing _ = Nothing
runMyMonad (Just val) (f:fs) = runMyMonad (f val) fs

Dan itu akan digunakan seperti ini:

print (runMyMonad (Just 160) myArray1)
warga negara1
sumber
1
Sangat rapi! Jadi bind hanyalah cara untuk mengevaluasi berbagai fungsi dengan konteks, secara berurutan, pada input dengan konteks :)
Musa Al-hassy
>>=adalah operator
user2418306
1
Saya pikir analogi "susunan fungsi" tidak banyak menjelaskan. Jika \x -> x >>= k >>= l >>= marray fungsi, demikian juga h . g . f, yang tidak melibatkan monads sama sekali.
duplode
kita dapat mengatakan bahwa functors , apakah monadik, aplikatif atau polos, adalah tentang "aplikasi yang dibumbui" . 'aplikatif' menambah rantai, dan 'monad' menambah ketergantungan (yaitu membuat langkah perhitungan selanjutnya tergantung pada hasil dari langkah perhitungan sebelumnya).
Will Ness
3

Dalam istilah OO, monad adalah wadah yang lancar.

Persyaratan minimum adalah definisi class <A> Somethingyang mendukung konstruktor Something(A a)dan setidaknya satu metodeSomething<B> flatMap(Function<A, Something<B>>)

Dapat diperdebatkan, ini juga diperhitungkan jika kelas monad Anda memiliki metode apa pun dengan tanda tangan Something<B> work()yang mempertahankan aturan kelas - pembuat kompiler dalam flatMap pada waktu kompilasi.

Mengapa monad bermanfaat? Karena itu adalah wadah yang memungkinkan operasi berantai yang melestarikan semantik. Misalnya, Optional<?>mempertahankan semantik isPresent untuk Optional<String>, Optional<Integer>, Optional<MyClass>, dll

Sebagai contoh kasar,

Something<Integer> i = new Something("a")
  .flatMap(doOneThing)
  .flatMap(doAnother)
  .flatMap(toInt)

Catatan kita mulai dengan string dan diakhiri dengan integer. Cukup keren.

Dalam OO, mungkin perlu sedikit melambaikan tangan, tetapi metode apa pun pada Sesuatu yang mengembalikan subkelas Sesuatu lainnya memenuhi kriteria fungsi wadah yang mengembalikan wadah dari jenis aslinya.

Itulah cara Anda mempertahankan semantik - artinya makna dan operasi wadah tidak berubah, mereka hanya membungkus dan meningkatkan objek di dalam wadah.

rampok
sumber
2

Monads dalam penggunaan tipikal adalah fungsional yang setara dengan mekanisme penanganan pengecualian pemrograman prosedural.

Dalam bahasa prosedural modern, Anda menempatkan penangan pengecualian di sekitar urutan pernyataan, yang salah satu di antaranya bisa melempar pengecualian. Jika salah satu dari pernyataan melempar pengecualian, eksekusi normal dari urutan pernyataan berhenti dan transfer ke pengendali pengecualian.

Namun, bahasa pemrograman fungsional secara filosofis menghindari fitur penanganan pengecualian karena sifat "goto" seperti mereka. Perspektif pemrograman fungsional adalah bahwa fungsi tidak boleh memiliki "efek samping" seperti pengecualian yang mengganggu aliran program.

Pada kenyataannya, efek samping tidak dapat dikesampingkan di dunia nyata terutama karena I / O. Monads dalam pemrograman fungsional digunakan untuk menangani hal ini dengan mengambil satu set panggilan fungsi berantai (yang mana mana mungkin menghasilkan hasil yang tidak terduga) dan mengubah setiap hasil yang tidak terduga menjadi data yang dienkapsulasi yang masih dapat mengalir dengan aman melalui panggilan fungsi yang tersisa.

Aliran kontrol dipertahankan tetapi kejadian yang tidak terduga ini dikemas dan ditangani dengan aman.

David K. Hess
sumber
2

Penjelasan Monads sederhana dengan studi kasus Marvel ada di sini .

Monads adalah abstraksi yang digunakan untuk mengurutkan fungsi-fungsi dependen yang efektif. Efektif di sini berarti mereka mengembalikan jenis dalam bentuk F [A] misalnya Opsi [A] di mana Opsi adalah F, yang disebut konstruktor tipe. Mari kita lihat ini dalam 2 langkah sederhana

  1. Komposisi Fungsi di bawah ini adalah transitif. Jadi untuk beralih dari A ke CI dapat menulis A => B dan B => C.
 A => C   =   A => B  andThen  B => C

masukkan deskripsi gambar di sini

  1. Namun, jika fungsi mengembalikan jenis efek seperti Opsi [A] yaitu A => F [B] komposisi tidak berfungsi untuk pergi ke B kita perlu A => B tetapi kita memiliki A => F [B].
    masukkan deskripsi gambar di sini

    Kami membutuhkan operator khusus, "bind" yang tahu cara menggabungkan fungsi-fungsi ini yang mengembalikan F [A].

 A => F[C]   =   A => F[B]  bind  B => F[C]

Fungsi "bind" didefinisikan untuk F tertentu .

Ada juga "return" , dari tipe A => F [A] untuk setiap A , yang didefinisikan untuk F tertentu juga. Untuk menjadi Monad, F harus memiliki dua fungsi yang ditentukan untuknya.

Dengan demikian kita dapat membangun fungsi efektif A => F [B] dari fungsi murni A => B ,

 A => F[B]   =   A => B  andThen  return

tetapi F yang diberikan juga dapat mendefinisikan fungsi khusus "bawaan" buram jenisnya sehingga pengguna tidak dapat menentukan sendiri (dalam bahasa murni ), seperti

  • "acak" ( Rentang => Acak [Int] )
  • "print" ( String => IO [()] )
  • "coba ... tangkap", dll.
Ira
sumber
2

Saya berbagi pemahaman saya tentang Monads, yang secara teori mungkin tidak sempurna. Monads adalah tentang propagasi konteks . Monad adalah, Anda mendefinisikan beberapa konteks untuk beberapa data (atau tipe data), dan kemudian menentukan bagaimana konteks itu akan dibawa dengan data di sepanjang pipa pemrosesan. Dan mendefinisikan propagasi konteks sebagian besar tentang mendefinisikan bagaimana menggabungkan beberapa konteks (dari tipe yang sama). Menggunakan Monads juga berarti memastikan bahwa konteks ini tidak secara tidak sengaja dihapus dari data. Di sisi lain, data tanpa konteks lainnya dapat dibawa ke konteks baru atau yang sudah ada. Maka konsep sederhana ini dapat digunakan untuk memastikan kompilasi ketepatan waktu suatu program.

Gulshan
sumber
1

Lihat jawaban saya untuk "Apa itu monad?"

Itu dimulai dengan sebuah contoh yang memotivasi, bekerja melalui contoh itu, mengambil contoh dari sebuah monad, dan secara resmi mendefinisikan "monad".

Ini mengasumsikan tidak memiliki pengetahuan tentang pemrograman fungsional dan menggunakan pseudocode dengan function(argument) := expressionsintaks dengan ekspresi sesederhana mungkin.

Program C ++ ini merupakan implementasi dari pseudocode monad. (Untuk referensi: Madalah konstruktor tipe, feedadalah operasi "bind", dan wrapmerupakan operasi "return".)

#include <iostream>
#include <string>

template <class A> class M
{
public:
    A val;
    std::string messages;
};

template <class A, class B>
M<B> feed(M<B> (*f)(A), M<A> x)
{
    M<B> m = f(x.val);
    m.messages = x.messages + m.messages;
    return m;
}

template <class A>
M<A> wrap(A x)
{
    M<A> m;
    m.val = x;
    m.messages = "";
    return m;
}

class T {};
class U {};
class V {};

M<U> g(V x)
{
    M<U> m;
    m.messages = "called g.\n";
    return m;
}

M<T> f(U x)
{
    M<T> m;
    m.messages = "called f.\n";
    return m;
}

int main()
{
    V x;
    M<T> m = feed(f, feed(g, wrap(x)));
    std::cout << m.messages;
}
Yordania
sumber
0

Dari sudut pandang praktis (meringkas apa yang telah dikatakan dalam banyak jawaban sebelumnya dan artikel terkait), bagi saya tampaknya salah satu "tujuan" (atau kegunaan) mendasar dari monad adalah untuk meningkatkan ketergantungan yang tersirat dalam doa metode rekursif. alias komposisi fungsi (yaitu ketika f1 memanggil f2 memanggil f3, f3 perlu dievaluasi sebelum f2 sebelum f1) untuk mewakili komposisi berurutan dengan cara alami, terutama dalam konteks model evaluasi malas (yaitu, komposisi sekuensial sebagai urutan polos , misalnya "f3 (); f2 (); f1 ();" dalam C - triknya sangat jelas jika Anda memikirkan kasus di mana f3, f2 dan f1 sebenarnya tidak mengembalikan apa pun [rantai mereka sebagai f1 (f2 (f3)) buatan, murni dimaksudkan untuk membuat urutan]).

Ini sangat relevan ketika efek samping terlibat, yaitu ketika beberapa keadaan diubah (jika f1, f2, f3 tidak memiliki efek samping, tidak masalah dalam urutan apa mereka dievaluasi; yang merupakan properti besar murni bahasa fungsional, untuk dapat memparalelkan perhitungan tersebut misalnya). Semakin murni fungsinya, semakin baik.

Saya pikir dari sudut pandang sempit itu, monad dapat dilihat sebagai gula sintaksis untuk bahasa yang mendukung evaluasi malas (yang mengevaluasi hal-hal hanya ketika benar-benar diperlukan, mengikuti pesanan yang tidak bergantung pada penyajian kode), dan yang tidak memiliki cara lain untuk mewakili komposisi berurutan. Hasil bersihnya adalah bahwa bagian-bagian kode yang "tidak murni" (yaitu yang memang memiliki efek samping) dapat disajikan secara alami, dengan cara yang imperatif, namun dipisahkan secara bersih dari fungsi murni (tanpa efek samping), yang dapat dievaluasi dengan malas.

Ini hanya satu aspek, seperti yang diperingatkan di sini .

novis
sumber
0

Penjelasan paling sederhana yang dapat saya pikirkan adalah bahwa monad adalah cara menyusun fungsi dengan hasil hiasan (alias komposisi Kleisli). Fungsi "embelished" memiliki tanda tangan di a -> (b, smth)mana adan btipe (berpikir Int, Bool) yang mungkin berbeda satu sama lain, tetapi tidak harus - dan smthmerupakan "konteks" atau "embelishment".

Jenis fungsi ini juga dapat ditulis di a -> m bmana msetara dengan "embelishment" smth. Jadi ini adalah fungsi yang mengembalikan nilai dalam konteks (pikirkan fungsi yang mencatat tindakan mereka, di mana smthpesan logging; atau fungsi yang melakukan input \ output dan hasilnya tergantung pada hasil dari tindakan IO).

Monad adalah antarmuka ("typeclass") yang membuat implementer memberi tahu cara menyusun fungsi tersebut. Implementer perlu mendefinisikan fungsi komposisi (a -> m b) -> (b -> m c) -> (a -> m c)untuk semua jenis myang ingin mengimplementasikan antarmuka (ini adalah komposisi Kleisli).

Jadi, jika kita mengatakan bahwa kita memiliki tipe tuple yang (Int, String)mewakili hasil perhitungan pada Ints yang juga mencatat tindakan mereka, dengan (_, String)menjadi "embelishment" - log dari tindakan - dan dua fungsi increment :: Int -> (Int, String)dan twoTimes :: Int -> (Int, String)kami ingin mendapatkan fungsi incrementThenDouble :: Int -> (Int, String)yang merupakan komposisi dari dua fungsi yang juga memperhitungkan log.

Pada contoh yang diberikan, implementasi monad dari dua fungsi berlaku untuk nilai integer 2 incrementThenDouble 2(yang sama dengan twoTimes (increment 2)) akan kembali (6, " Adding 1. Doubling 3.")untuk hasil perantara increment 2sama dengan (3, " Adding 1.")dan twoTimes 3sama dengan(6, " Doubling 3.")

Dari fungsi komposisi Kleisli ini seseorang dapat memperoleh fungsi monadik yang biasa.

RedPoppy
sumber