Apa itu monad?


1415

Setelah secara singkat melihat Haskell baru-baru ini, apa yang akan menjadi penjelasan singkat, ringkas, praktis tentang apa dasarnya monad?

Saya telah menemukan sebagian besar penjelasan yang saya temui tidak masuk akal dan kurang detail praktis.


12
Eric Lippert menulis jawaban untuk pertanyaan ini ( stackoverflow.com/questions/2704652/… ), yang disebabkan oleh beberapa masalah, tinggal di halaman terpisah.
P Shved

70
Berikut ini pengantar baru menggunakan javascript - Saya merasa sangat mudah dibaca.
Benjol



2
Monad adalah array fungsi dengan operasi pembantu. Lihat jawaban ini
cibercitizen1

Jawaban:


1060

Pertama: Istilah monad agak kosong jika Anda bukan ahli matematika. Istilah alternatif adalah pembangun perhitungan yang sedikit lebih deskriptif dari apa yang sebenarnya berguna bagi mereka.

Anda meminta contoh-contoh praktis:

Contoh 1: Pemahaman daftar :

[x*2 | x<-[1..10], odd x]

Ungkapan ini mengembalikan ganda dari semua angka ganjil dalam kisaran dari 1 hingga 10. Sangat berguna!

Ternyata ini benar-benar hanya gula sintaksis untuk beberapa operasi dalam daftar monad. Pemahaman daftar yang sama dapat ditulis sebagai:

do
   x <- [1..10]
   guard (odd x)
   return (x * 2)

Atau bahkan:

[1..10] >>= (\x -> guard (odd x) >> return (x*2))

Contoh 2: Input / Output :

do
   putStrLn "What is your name?"
   name <- getLine
   putStrLn ("Welcome, " ++ name ++ "!")

Kedua contoh menggunakan monads, pembangun perhitungan AKA. Tema umum adalah bahwa rantai monad beroperasi dalam beberapa cara yang spesifik dan bermanfaat. Dalam pemahaman daftar, operasi dirantai sedemikian sehingga jika operasi mengembalikan daftar, maka operasi berikut dilakukan pada setiap item dalam daftar. Mono IO di sisi lain melakukan operasi secara berurutan, tetapi melewati "variabel tersembunyi" bersama, yang mewakili "keadaan dunia", yang memungkinkan kita untuk menulis kode I / O dengan cara fungsional murni.

Ternyata pola operasi chaining cukup berguna dan digunakan untuk banyak hal berbeda di Haskell.

Contoh lain adalah pengecualian: Menggunakan Errormonad, operasi dirantai sedemikian rupa sehingga dilakukan secara berurutan, kecuali jika kesalahan dilemparkan, dalam hal ini sisa rantai ditinggalkan.

Sintaksis daftar-pahami dan do-notasi adalah gula sintaksis untuk operasi perangkaian menggunakan >>=operator. Monad pada dasarnya hanya tipe yang mendukung >>=operator.

Contoh 3: Pengurai

Ini adalah pengurai yang sangat sederhana yang mem-parsing string yang dikutip atau angka:

parseExpr = parseString <|> parseNumber

parseString = do
        char '"'
        x <- many (noneOf "\"")
        char '"'
        return (StringValue x)

parseNumber = do
    num <- many1 digit
    return (NumberValue (read num))

Operasi char,, digitdll. Cukup sederhana. Mereka cocok atau tidak cocok. Sihir adalah monad yang mengelola aliran kontrol: Operasi dilakukan secara berurutan hingga pertandingan gagal, dalam hal ini monad akan mundur ke yang terbaru <|>dan mencoba opsi berikutnya. Sekali lagi, cara merantai operasi dengan beberapa semantik tambahan yang bermanfaat.

Contoh 4: Pemrograman asinkron

Contoh di atas ada di Haskell, tetapi ternyata F # juga mendukung monads. Contoh ini dicuri dari Don Syme :

let AsyncHttp(url:string) =
    async {  let req = WebRequest.Create(url)
             let! rsp = req.GetResponseAsync()
             use stream = rsp.GetResponseStream()
             use reader = new System.IO.StreamReader(stream)
             return reader.ReadToEnd() }

Metode ini mengambil halaman web. Punch line adalah penggunaan GetResponseAsync- ia benar-benar menunggu respons pada utas terpisah, sedangkan utas utama kembali dari fungsi. Tiga baris terakhir dieksekusi pada utas menelurkan ketika respons telah diterima.

Dalam sebagian besar bahasa lain Anda harus secara eksplisit membuat fungsi terpisah untuk baris yang menangani respons. The asyncmonad mampu "split" blok sendiri dan menunda pelaksanaan paruh kedua. ( async {}Sintaks menunjukkan bahwa aliran kontrol di blok ditentukan oleh asyncmonad.)

Bagaimana mereka bekerja

Jadi, bagaimana bisa monad melakukan semua hal aliran kendali yang mewah ini? Apa yang sebenarnya terjadi pada do-block (atau ekspresi komputasi sebagaimana mereka disebut dalam F #), adalah bahwa setiap operasi (pada dasarnya setiap baris) dibungkus dalam fungsi anonim yang terpisah. Fungsi-fungsi ini kemudian digabungkan menggunakan bindoperator (dieja >>=dalam Haskell). Karena bindoperasi menggabungkan fungsi, ia dapat menjalankannya sesuai keinginan: secara berurutan, beberapa kali, secara terbalik, membuang beberapa, mengeksekusi beberapa di utas terpisah ketika terasa seperti itu dan seterusnya.

Sebagai contoh, ini adalah versi diperluas dari kode-IO dari contoh 2:

putStrLn "What is your name?"
>>= (\_ -> getLine)
>>= (\name -> putStrLn ("Welcome, " ++ name ++ "!"))

Ini lebih buruk, tetapi juga lebih jelas apa yang sebenarnya terjadi. The >>=operator adalah bahan ajaib: Dibutuhkan nilai (di sisi kiri) dan menggabungkan dengan fungsi (di sisi kanan), untuk menghasilkan nilai baru. Nilai baru ini kemudian diambil oleh >>=operator berikutnya dan sekali lagi dikombinasikan dengan fungsi untuk menghasilkan nilai baru. >>=dapat dilihat sebagai evaluator mini.

Catatan yang >>=kelebihan beban untuk berbagai jenis, sehingga setiap monad memiliki implementasi sendiri >>=. (Semua operasi dalam rantai harus dari jenis monad yang sama, jika tidak, >>=operator tidak akan bekerja.)

Implementasi yang paling sederhana dari >>=hanya mengambil nilai di sebelah kiri dan menerapkannya pada fungsi di sebelah kanan dan mengembalikan hasilnya, tetapi seperti yang dikatakan sebelumnya, apa yang membuat seluruh pola berguna adalah ketika ada sesuatu yang terjadi dalam implementasi monad dari >>=.

Ada beberapa kepintaran tambahan dalam bagaimana nilai-nilai dilewatkan dari satu operasi ke operasi berikutnya, tetapi ini membutuhkan penjelasan yang lebih dalam dari sistem tipe Haskell.

Menyimpulkan

Dalam istilah Haskell, monad adalah tipe parameter yang merupakan turunan dari kelas tipe Monad, yang mendefinisikan >>=bersama dengan beberapa operator lainnya. Dalam istilah awam, monad hanyalah tipe >>=operasi yang didefinisikan.

Dalam dirinya sendiri >>=hanyalah cara rumit dari fungsi chaining, tetapi dengan kehadiran notasi yang menyembunyikan "plumbing", operasi monadik ternyata menjadi abstraksi yang sangat bagus dan berguna, berguna banyak tempat dalam bahasa, dan berguna untuk membuat bahasa mini Anda sendiri dalam bahasa tersebut.

Mengapa monad sulit?

Bagi banyak pelajar Haskell, monad adalah hambatan yang mereka pukul seperti dinding bata. Bukannya monad itu sendiri kompleks, tetapi implementasinya bergantung pada banyak fitur Haskell canggih lainnya seperti tipe parameter, kelas tipe, dan sebagainya. Masalahnya adalah bahwa Haskell I / O didasarkan pada monad, dan I / O mungkin adalah salah satu hal pertama yang ingin Anda pahami ketika mempelajari bahasa baru - lagipula, tidak terlalu menyenangkan untuk membuat program yang tidak menghasilkan apa-apa keluaran. Saya tidak punya solusi langsung untuk masalah ayam-dan-telur ini, kecuali memperlakukan saya / O seperti "keajaiban terjadi di sini" sampai Anda memiliki cukup pengalaman dengan bagian bahasa lain. Maaf.

Blog luar biasa di monads: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html


66
Sebagai seseorang yang memiliki banyak masalah dalam memahami monad, saya dapat mengatakan bahwa jawaban ini membantu .. sedikit. Namun, masih ada beberapa hal yang saya tidak mengerti. Dengan cara apa pemahaman daftar itu sebuah monad? Apakah ada bentuk contoh yang diperluas? Hal lain yang benar-benar mengganggu saya tentang kebanyakan penjelasan monad, termasuk yang satu ini - Apakah mereka terus mencampuradukkan "apa itu monad?" dengan "apa gunanya monad?" dan "Bagaimana monad diimplementasikan?". Anda melompat hiu itu ketika Anda menulis "Monad pada dasarnya hanya tipe yang mendukung operator >> =." Yang baru saja saya ...
Breton

83
Juga saya tidak setuju dengan kesimpulan Anda tentang mengapa monad sulit. Jika monad itu sendiri tidak rumit, maka Anda harus dapat menjelaskan apa itu tanpa bagasi. Saya tidak ingin tahu tentang implementasinya ketika saya mengajukan pertanyaan "Apa itu monad", saya ingin tahu gatal apa yang dimaksudkan untuk menggaruk. Sejauh ini sepertinya jawabannya adalah "Karena penulis haskell adalah sadomasochis dan memutuskan bahwa Anda harus melakukan sesuatu yang bodoh untuk menyelesaikan hal-hal sederhana, jadi Anda HARUS belajar monad untuk menggunakan haskell, bukan karena mereka dengan cara apa pun berguna dalam diri mereka sendiri "...
Breton

70
Tapi .. itu tidak benar, bukan? Saya pikir monad sulit karena tidak ada yang bisa mencari cara untuk menjelaskannya tanpa terjebak dalam rincian implementasi yang membingungkan. Maksud saya .. apa itu bus sekolah? Ini adalah platform logam dengan perangkat di bagian depan yang mengkonsumsi produk minyak olahan untuk mendorong dalam siklus beberapa piston logam, yang pada gilirannya memutar poros engkol yang melekat pada beberapa roda gigi yang menggerakkan beberapa roda. Roda telah menggembungkan kantong karet di sekitar mereka yang berinteraksi dengan permukaan aspal untuk menyebabkan koleksi kursi bergerak maju. Kursi bergerak maju karena ...
Breton

130
Saya membaca semua ini dan masih tidak tahu apa itu monad, selain dari fakta bahwa itu adalah sesuatu yang tidak cukup dimengerti oleh programmer Haskell untuk dijelaskan. Contoh-contohnya tidak banyak membantu, mengingat bahwa ini adalah semua hal yang dapat dilakukan tanpa monad, dan jawaban ini tidak menjelaskan bagaimana monad menjadikannya lebih mudah, hanya lebih membingungkan. Satu bagian dari jawaban ini yang hampir berguna adalah ketika gula sintaksis dari contoh # 2 dihilangkan. Saya katakan mendekati karena, selain dari baris pertama, ekspansi tidak memiliki kemiripan nyata dengan aslinya.
Laurence Gonsalves

81
Masalah lain yang tampaknya endemik untuk penjelasan monad adalah bahwa itu ditulis dalam Haskell. Saya tidak mengatakan Haskell adalah bahasa yang buruk - saya katakan itu adalah bahasa yang buruk untuk menjelaskan monad. Jika saya tahu Haskell saya sudah mengerti monad, jadi jika Anda ingin menjelaskan monad, mulailah dengan menggunakan bahasa yang orang-orang yang tidak tahu monad lebih mungkin untuk mengerti. Jika Anda harus menggunakan Haskell, jangan gunakan gula sintaksis sama sekali - gunakan subset terkecil dari bahasa yang Anda bisa, dan jangan menganggap pemahaman tentang Haskell IO.
Laurence Gonsalves

712

Menjelaskan "apa itu monad" agak mirip dengan mengatakan "apa itu angka?" Kami menggunakan angka setiap saat. Tapi bayangkan Anda bertemu seseorang yang tidak tahu apa-apa tentang angka. Bagaimana sih Anda akan menjelaskan apa nomor yang? Dan bagaimana Anda bahkan mulai menjelaskan mengapa itu mungkin berguna?

Apa itu monad? Jawaban singkatnya: Ini adalah cara khusus untuk merantai operasi bersama.

Intinya, Anda menulis langkah-langkah eksekusi dan menghubungkannya bersama dengan "fungsi bind". (Dalam Haskell, ini dinamai >>=.) Anda dapat menulis sendiri panggilan ke operator bind, atau Anda dapat menggunakan gula sintaks yang membuat kompiler memasukkan fungsi panggilan tersebut untuk Anda. Namun demikian, setiap langkah dipisahkan oleh panggilan ke fungsi bind ini.

Jadi fungsi ikatnya seperti titik koma; itu memisahkan langkah-langkah dalam suatu proses. Tugas fungsi bind adalah mengambil output dari langkah sebelumnya, dan memasukkannya ke langkah berikutnya.

Itu tidak terdengar terlalu sulit, bukan? Tetapi ada lebih dari satu jenis monad. Mengapa? Bagaimana?

Nah, fungsi bind bisa mengambil hasil dari satu langkah, dan memasukkannya ke langkah berikutnya. Tetapi jika itu "semua" yang dilakukan monad ... itu sebenarnya tidak terlalu berguna. Dan itu penting untuk dipahami: Setiap monad bermanfaat melakukan hal lain selain hanya menjadi monad. Setiap monad yang bermanfaat memiliki "kekuatan khusus", yang membuatnya unik.

(Monad yang tidak melakukan apa - apa istimewa disebut "identitas monad". Agak seperti fungsi identitas, ini terdengar seperti hal yang sama sekali tidak ada gunanya, namun ternyata bukan ... Tapi itu cerita lain ™.)

Pada dasarnya, setiap monad memiliki implementasi fungsi bind sendiri. Dan Anda dapat menulis fungsi bind sedemikian rupa sehingga tidak terjadi apa-apa di antara langkah-langkah eksekusi. Sebagai contoh:

  • Jika setiap langkah mengembalikan indikator keberhasilan / kegagalan, Anda dapat mengikat menjalankan langkah berikutnya hanya jika yang sebelumnya berhasil. Dengan cara ini, langkah yang gagal membatalkan seluruh urutan "secara otomatis", tanpa pengujian bersyarat dari Anda. (The Failure Monad .)

  • Memperluas gagasan ini, Anda dapat menerapkan "pengecualian". ( Monad Kesalahan atau Monad Pengecualian .) Karena Anda mendefinisikannya sendiri alih-alih sebagai fitur bahasa, Anda dapat menentukan cara kerjanya. (Misalnya, mungkin Anda ingin mengabaikan dua pengecualian pertama dan hanya membatalkan ketika pengecualian ketiga dilemparkan.)

  • Anda dapat membuat setiap langkah mengembalikan beberapa hasil , dan meminta fungsi simpul diikat, memberi makan masing-masing ke langkah berikutnya untuk Anda. Dengan cara ini, Anda tidak harus terus menulis loop di semua tempat ketika berhadapan dengan banyak hasil. Fungsi bind "secara otomatis" melakukan semua itu untuk Anda. ( Daftar Monad .)

  • Selain meneruskan "hasil" dari satu langkah ke langkah lainnya, Anda dapat memiliki fungsi bind untuk meneruskan data tambahan juga. Data ini sekarang tidak muncul dalam kode sumber Anda, tetapi Anda masih dapat mengaksesnya dari mana saja, tanpa harus meneruskannya secara manual ke setiap fungsi. (The Reader Monad .)

  • Anda dapat membuatnya sehingga "data tambahan" dapat diganti. Ini memungkinkan Anda untuk mensimulasikan pembaruan yang merusak , tanpa benar-benar melakukan pembaruan yang merusak. (The State Monad dan sepupunya Writer Monad .)

  • Karena Anda hanya mensimulasikan pembaruan destruktif, Anda dapat melakukan hal-hal sepele yang tidak mungkin dilakukan dengan pembaruan destruktif nyata . Misalnya, Anda dapat membatalkan pembaruan terakhir , atau kembali ke versi yang lebih lama .

  • Anda dapat membuat monad tempat penghitungan dapat dijeda , sehingga Anda dapat menjeda program Anda, masuk dan mengotak-atik data keadaan internal, dan kemudian melanjutkannya.

  • Anda dapat menerapkan "kelanjutan" sebagai monad. Ini memungkinkan Anda untuk menghancurkan pikiran orang!

Semua ini dan banyak lagi dimungkinkan dengan monad. Tentu saja, semua ini juga sangat mungkin terjadi tanpa monad juga. Ini hanya secara drastis lebih mudah menggunakan monad.


13
Saya menghargai jawaban Anda — terutama konsesi terakhir bahwa semua ini tentu saja mungkin juga tanpa monad. Satu titik yang akan dibuat adalah bahwa hal itu sebagian besar lebih mudah dengan monads, tapi sering tidak seefisien melakukannya tanpa mereka. Setelah Anda perlu melibatkan transformer, pelapisan fungsi tambahan (dan objek fungsi dibuat) memiliki biaya yang sulit dilihat dan dikendalikan, dibuat tidak terlihat oleh sintaksis yang pintar.
seh

1
Setidaknya di Haskell, sebagian besar overhead monad dilucuti oleh optimiser. Jadi, satu-satunya "biaya" nyata adalah kekuatan otak yang dibutuhkan. (Ini tidak signifikan jika "pemeliharaan" adalah sesuatu yang Anda pedulikan.) Tetapi biasanya, monad membuat segalanya lebih mudah , bukan lebih sulit. (Kalau tidak, mengapa Anda mau repot-repot?)
MathematicalOrchid

Saya tidak yakin apakah Haskell mendukung ini atau tidak, tetapi secara matematis Anda dapat mendefinisikan monad baik dalam hal >> = dan kembali atau bergabung dan ap. >> = dan kembali adalah apa yang membuat monad praktis berguna tetapi bergabung dan ap memberikan pemahaman yang lebih intuitif tentang apa itu monad.
Daftar Jeremy

15
Berasal dari latar belakang pemrograman non-matematika, non-fungsional, jawaban ini paling masuk akal bagi saya.
jrahhali

10
Ini adalah jawaban pertama yang benar-benar memberi saya ide tentang apa itu monad. Terima kasih telah menemukan cara untuk menjelaskannya!
robotmay

186

Sebenarnya, bertentangan dengan pemahaman umum tentang Monads, mereka tidak ada hubungannya dengan negara. Monads hanyalah cara untuk membungkus barang-barang dan menyediakan metode untuk melakukan operasi pada barang yang dibungkus tanpa membuka bungkusnya.

Misalnya, Anda bisa membuat tipe untuk membungkus yang lain, di Haskell:

data Wrapped a = Wrap a

Untuk membungkus barang yang kami tentukan

return :: a -> Wrapped a
return x = Wrap x

Untuk melakukan operasi tanpa membuka, katakan Anda memiliki fungsi f :: a -> b, maka Anda dapat melakukan ini untuk mengangkat fungsi itu untuk bertindak pada nilai yang dibungkus:

fmap :: (a -> b) -> (Wrapped a -> Wrapped b)
fmap f (Wrap x) = Wrap (f x)

Hanya itu yang perlu dipahami. Namun, ternyata ada fungsi yang lebih umum untuk melakukan pengangkatan ini , yaitu bind:

bind :: (a -> Wrapped b) -> (Wrapped a -> Wrapped b)
bind f (Wrap x) = f x

binddapat melakukan sedikit lebih banyak daripada fmap, tetapi tidak sebaliknya. Sebenarnya, fmapdapat didefinisikan hanya dari segi binddan return. Jadi, ketika mendefinisikan sebuah monad .. Anda memberikan tipenya (ini dia Wrapped a) dan kemudian mengatakan bagaimana itu returndan bindoperasi bekerja.

Yang keren adalah bahwa ini ternyata menjadi pola umum sehingga muncul di semua tempat, keadaan enkapsulasi dengan cara murni hanya salah satunya.

Untuk artikel yang bagus tentang bagaimana monad dapat digunakan untuk memperkenalkan dependensi fungsional dan dengan demikian mengontrol urutan evaluasi, seperti yang digunakan dalam monad IO Haskell, lihat IO Inside .

Sedangkan untuk memahami monad, jangan terlalu khawatir tentang hal itu. Baca tentang mereka apa yang menurut Anda menarik dan jangan khawatir jika Anda tidak segera mengerti. Maka hanya menyelam dalam bahasa seperti Haskell adalah cara untuk pergi. Monad adalah salah satu dari hal-hal ini di mana pemahaman mengalir ke otak Anda dengan latihan, suatu hari Anda tiba-tiba menyadari Anda memahaminya.


-> adalah asosiatif-kanan, aplikasi fungsi pencerminan, yang asosiatif-kiri, jadi membiarkan tanda kurung tidak membuat perbedaan di sini.
Matthias Benkard

1
Saya kira ini bukan penjelasan yang sangat bagus. Monad hanyalah A way? oke, ke arah mana? Mengapa saya tidak merangkum menggunakan kelas, bukan monad?
Breton

4
@ mb21: Jika Anda hanya menunjukkan bahwa ada terlalu banyak tanda kurung, catat bahwa a-> b-> c sebenarnya hanya kependekan dari a -> (b-> c). Menulis contoh khusus ini sebagai (a -> b) -> (Ta -> Tb) secara tegas hanya menambahkan karakter yang tidak perlu, tetapi secara moral "hal yang tepat untuk dilakukan" karena menekankan bahwa fmap memetakan fungsi tipe a -> b ke fungsi ketik Ta -> Tb. Dan awalnya, itulah yang dilakukan oleh functors dalam teori kategori dan dari situlah monad berasal.
Nikolaj-K

1
Jawaban ini menyesatkan. Beberapa monad tidak memiliki "pembungkus" sama sekali, fungsi semacam itu dari nilai tetap.

1
@DanMandel Monads adalah pola desain yang memasok pembungkus datatype sendiri. Monads dirancang dengan cara abstrak kode boilerplate. Jadi saat Anda memanggil Monad dalam kode Anda, hal itu ada di balik layar hal-hal yang Anda tidak ingin khawatirkan. Pikirkan tentang Nullable <T> atau IEnumerable <T>, apa yang mereka lakukan di balik layar? Itu Monad.
sksallaj

168

Tapi, Anda bisa menemukan Monads!

Sigfpe mengatakan:

Tetapi semua ini memperkenalkan monad sebagai sesuatu yang esoteris yang perlu penjelasan. Tapi yang ingin saya katakan adalah bahwa mereka tidak esoteris sama sekali. Bahkan, dihadapkan dengan berbagai masalah dalam pemrograman fungsional Anda pasti akan dituntun, pasti, untuk solusi tertentu, yang semuanya adalah contoh dari monad. Bahkan, saya berharap membuat Anda menemukan mereka sekarang jika Anda belum melakukannya. Maka langkah kecil untuk memperhatikan bahwa semua solusi ini sebenarnya adalah solusi yang sama dalam penyamaran. Dan setelah membaca ini, Anda mungkin berada dalam posisi yang lebih baik untuk memahami dokumen lain tentang monad karena Anda akan mengenali semua yang Anda lihat sebagai sesuatu yang sudah Anda temukan.

Banyak masalah yang coba diselesaikan monad terkait dengan masalah efek samping. Jadi kita akan mulai dengan mereka. (Perhatikan bahwa monad memungkinkan Anda melakukan lebih dari sekadar menangani efek samping, khususnya banyak jenis objek penampung yang dapat dipandang sebagai monad. Beberapa pengantar monad merasa sulit untuk merekonsiliasi dua penggunaan monad yang berbeda ini dan berkonsentrasi hanya pada satu atau yang lain.)

Dalam bahasa pemrograman imperatif seperti C ++, fungsi berperilaku tidak seperti fungsi matematika. Sebagai contoh, misalkan kita memiliki fungsi C ++ yang mengambil argumen floating point tunggal dan mengembalikan hasil floating point. Secara dangkal itu mungkin tampak sedikit seperti pemetaan fungsi matematika real to real, tetapi fungsi C ++ dapat melakukan lebih dari sekadar mengembalikan angka yang bergantung pada argumennya. Itu dapat membaca dan menulis nilai-nilai variabel global serta menulis output ke layar dan menerima input dari pengguna. Namun, dalam bahasa fungsional murni, suatu fungsi hanya bisa membaca apa yang dipasok padanya dalam argumennya dan satu-satunya cara ia dapat memiliki efek pada dunia adalah melalui nilai-nilai yang dikembalikannya.


9
... cara terbaik tidak hanya di internet, tetapi di mana saja. (Makalah asli Wadler Monads untuk pemrograman fungsional yang saya sebutkan dalam jawaban saya di bawah ini juga bagus.) Tidak ada satu pun dari zillions tutorial-by-analogi yang mendekati.
ShreevatsaR

13
Terjemahan JavaScript dari pos Sigfpe ini adalah cara baru terbaik untuk belajar monad, untuk orang-orang yang belum grok maju Haskell!
Sam Watkins

1
Inilah bagaimana saya belajar apa itu monad. Berjalan pembaca melalui proses menciptakan konsep sering kali merupakan cara terbaik untuk mengajarkan konsep.
Jordan

Namun, fungsi yang menerima objek layar sebagai argumen dan mengembalikan salinannya dengan teks yang dimodifikasi akan murni.
Dmitri Zaitsev

87

Monad adalah tipe data yang memiliki dua operasi: >>=(alias bind) dan return(alias unit). returnmengambil nilai sewenang-wenang dan menciptakan instance monad dengannya. >>=mengambil instance dari monad dan memetakan fungsi di atasnya. (Anda sudah dapat melihat bahwa monad adalah sejenis tipe data yang aneh, karena di sebagian besar bahasa pemrograman Anda tidak dapat menulis fungsi yang mengambil nilai arbitrer dan membuat jenis darinya. Monad menggunakan sejenis polimorfisme parametrik .)

Dalam notasi Haskell, antarmuka monad ditulis

class Monad m where
  return :: a -> m a
  (>>=) :: forall a b . m a -> (a -> m b) -> m b

Operasi-operasi ini seharusnya mematuhi "undang-undang" tertentu, tetapi itu tidak terlalu penting: "undang-undang" hanya mengkodifikasikan cara pelaksanaan operasi yang masuk akal harus berperilaku (pada dasarnya, itu >>=dan returnharus setuju tentang bagaimana nilai-nilai ditransformasikan menjadi contoh monad dan itu >>=asosiatif).

Monad bukan hanya tentang negara dan I / O: mereka abstrak pola komputasi yang umum yang mencakup bekerja dengan negara, I / O, pengecualian, dan non-determinisme. Mungkin monad paling sederhana untuk dipahami adalah daftar dan jenis opsi:

instance Monad [ ] where
    []     >>= k = []
    (x:xs) >>= k = k x ++ (xs >>= k)
    return x     = [x]

instance Monad Maybe where
    Just x  >>= k = k x
    Nothing >>= k = Nothing
    return x      = Just x

di mana []dan :adalah konstruktor daftar, ++adalah operator gabungan, dan Justdan Nothingadalah Maybekonstruktor. Kedua monad ini merangkum pola komputasi yang umum dan berguna pada masing-masing tipe data (perhatikan bahwa tidak ada hubungannya dengan efek samping atau I / O).

Anda benar-benar harus bermain-main menulis beberapa kode Haskell non-sepele untuk menghargai tentang apa itu monad dan mengapa mereka berguna.


Apa sebenarnya yang Anda maksud dengan "memetakan fungsi di atasnya"?
Casebash

Casebash, aku sengaja tidak resmi dalam pendahuluan. Lihat contoh di dekat bagian akhir untuk mengetahui arti "pemetaan fungsi".
Chris Conway

3
Monad bukan tipe data. Ini adalah aturan fungsi penulisan: stackoverflow.com/a/37345315/1614973
Dmitri Zaitsev

@DmitriZaitsev benar, Monads sebenarnya menyediakan tipe data datanya sendiri, tipe data
Monads

78

Anda harus terlebih dahulu memahami apa itu functor. Sebelum itu, pahami fungsi tingkat tinggi.

Fungsi tingkat tinggi hanyalah fungsi yang mengambil fungsi sebagai argumen.

Sebuah functor adalah setiap konstruksi jenis Tyang terdapat fungsi tingkat tinggi, sebut saja map, bahwa transformasi fungsi dari jenis a -> b(diberi dua jenis adan b) ke dalam fungsi T a -> T b. mapFungsi ini juga harus mematuhi hukum-hukum identitas dan komposisi sehingga ungkapan berikut ini berlaku untuk semua pdan q(notasi Haskell):

map id = id
map (p . q) = map p . map q

Misalnya, tipe konstruktor yang dipanggil Listadalah functor jika dilengkapi dengan fungsi tipe (a -> b) -> List a -> List byang mematuhi hukum di atas. Satu-satunya implementasi praktis sudah jelas. Fungsi yang dihasilkan List a -> List bberulang di atas daftar yang diberikan, memanggil (a -> b)fungsi untuk setiap elemen, dan mengembalikan daftar hasil.

Sebuah monad dasarnya hanya functor Tdengan dua metode ekstra, join, jenis T (T a) -> T a, dan unit(kadang-kadang disebut return, forkatau pure) tipe a -> T a. Untuk daftar di Haskell:

join :: [[a]] -> [a]
pure :: a -> [a]

Mengapa itu bermanfaat? Karena Anda bisa, misalnya, mappada daftar dengan fungsi yang mengembalikan daftar. Joinmengambil daftar daftar yang dihasilkan dan menggabungkannya. Listadalah monad karena ini mungkin.

Anda dapat menulis fungsi yang berfungsi map, kemudian join. Fungsi ini disebut bind, atau flatMap, atau (>>=), atau (=<<). Ini biasanya bagaimana instance monad diberikan dalam Haskell.

Monad harus memenuhi hukum tertentu, yaitu yang joinharus asosiatif. Ini berarti bahwa jika Anda memiliki nilai xtipe [[[a]]]maka join (join x)harus sama join (map join x). Dan pureharus menjadi identitas untuk joinitu join (pure x) == x.


3
sedikit tambahan untuk def 'fungsi urutan lebih tinggi': mereka dapat mengambil fungsi ATAU KEMBALI. Itu sebabnya mereka 'lebih tinggi' 'karena mereka melakukan sesuatu dengan diri mereka sendiri.
Kevin Won

9
Menurut definisi itu, penambahan adalah fungsi tingkat tinggi. Dibutuhkan angka dan mengembalikan fungsi yang menambahkan nomor itu ke yang lain. Jadi tidak, fungsi urutan lebih tinggi adalah fungsi ketat yang domainnya terdiri dari fungsi.
Apocalisp

Video ' Brian Beckman: Jangan Takut Monad ' mengikuti garis logika yang sama.
icc97

48

[Penafian: Saya masih mencoba sepenuhnya grok monad. Berikut ini adalah apa yang saya pahami sejauh ini. Jika itu salah, semoga seseorang yang berpengetahuan akan memanggilku di karpet.]

Arnar menulis:

Monads hanyalah cara untuk membungkus barang-barang dan menyediakan metode untuk melakukan operasi pada barang yang dibungkus tanpa membuka bungkusnya.

Tepat seperti itu. Idenya seperti ini:

  1. Anda mengambil semacam nilai dan membungkusnya dengan beberapa informasi tambahan. Sama seperti nilainya dari jenis tertentu (mis. Integer atau string), sehingga informasi tambahan dari jenis tertentu.

    Misalnya, informasi tambahan itu mungkin a Maybeatau a IO.

  2. Kemudian Anda memiliki beberapa operator yang memungkinkan Anda untuk beroperasi pada data yang dibungkus sambil membawa informasi tambahan itu. Operator-operator ini menggunakan informasi tambahan untuk memutuskan bagaimana mengubah perilaku operasi pada nilai yang dibungkus.

    Misalnya, a Maybe Intbisa a Just Intatau Nothing. Sekarang, jika Anda menambahkan a Maybe Intke Maybe Int, operator akan memeriksa untuk melihat apakah keduanya ada Just Intdi dalam, dan jika demikian, akan membuka bungkusnya Int, meneruskannya ke operator tambahan, membungkus ulang hasilnya Intmenjadi yang baru Just Int(yang valid Maybe Int), dan dengan demikian mengembalikan a Maybe Int. Tetapi jika salah satunya ada Nothingdi dalam, operator ini akan langsung kembali Nothing, yang lagi adalah valid Maybe Int. Dengan begitu, Anda dapat berpura-pura bahwa Maybe Intangka Anda hanya normal dan melakukan matematika reguler pada angka itu. Jika Anda mendapatkan Nothing, persamaan Anda masih akan menghasilkan hasil yang tepat - tanpa Anda harus membuang cek untuk di Nothingmana - mana .

Tetapi contohnya adalah apa yang terjadi Maybe. Jika informasi tambahan adalah IO, maka operator khusus yang ditentukan untuk IOs akan dipanggil, dan itu bisa melakukan sesuatu yang sama sekali berbeda sebelum melakukan penambahan. (Oke, menambahkan dua IO Ints bersama-sama mungkin tidak masuk akal - saya belum yakin.) (Juga, jika Anda memperhatikan Maybecontoh, Anda telah memperhatikan bahwa "membungkus nilai dengan barang tambahan" tidak selalu benar. Tetapi sulit tepatnya, benar, dan tepat tanpa bisa dipahami.)

Pada dasarnya, "monad" secara kasar berarti "pola" . Tetapi alih-alih sebuah buku yang penuh dengan Pola yang dijelaskan secara informal dan secara spesifik bernama, Anda sekarang memiliki konstruk bahasa - sintaks dan semua - yang memungkinkan Anda untuk mendeklarasikan pola baru sebagai hal dalam program Anda . (Ketidaktepatan di sini adalah semua pola harus mengikuti bentuk tertentu, jadi monad tidak cukup generik seperti pola. Tapi saya pikir itu istilah terdekat yang kebanyakan orang tahu dan mengerti.)

Dan itulah mengapa orang menganggap monad sangat membingungkan: karena mereka adalah konsep yang sangat umum. Untuk bertanya apa yang membuat sesuatu menjadi monad sama tidak jelasnya dengan bertanya apa yang membuat sesuatu menjadi suatu pola.

Tapi pikirkan implikasi dari memiliki dukungan sintaksis dalam bahasa untuk ide pola: daripada harus membaca buku Gang of Four dan menghafal konstruksi pola tertentu, Anda hanya menulis kode yang mengimplementasikan pola ini dalam agnostik, cara umum sekali dan kemudian Anda selesai! Anda kemudian dapat menggunakan kembali pola ini, seperti Pengunjung atau Strategi atau Façade atau apa pun, hanya dengan mendekorasi operasi dalam kode Anda dengannya, tanpa harus menerapkannya berulang kali!

Jadi itu sebabnya orang yang memahami monad menganggapnya sangat berguna : itu bukan konsep menara gading yang sombong intelektual bangga pada pemahaman (OK, tentu saja, teehee), tetapi sebenarnya membuat kode lebih sederhana.


12
Kadang-kadang penjelasan dari "pelajar" (seperti Anda) lebih relevan untuk pelajar lain daripada penjelasan yang berasal dari seorang ahli. Pembelajar berpikir sama :)
Adrian

Apa yang membuat sesuatu menjadi monad adalah adanya fungsi dengan tipe M (M a) -> M a. Fakta bahwa Anda dapat mengubahnya menjadi salah satu jenis M a -> (a -> M b) -> M badalah apa yang membuatnya berguna.
Daftar Jeremy

"Monad" secara kasar berarti "pola" ... tidak.
Terima kasih

44

Setelah banyak berjuang, saya pikir saya akhirnya mengerti monad. Setelah membaca ulang kritik saya yang panjang tentang jawaban yang terpilih paling banyak, saya akan menawarkan penjelasan ini.

Ada tiga pertanyaan yang perlu dijawab untuk memahami monad:

  1. Mengapa Anda membutuhkan monad?
  2. Apa itu monad?
  3. Bagaimana monad diimplementasikan?

Seperti yang saya catat dalam komentar asli saya, terlalu banyak penjelasan monad terperangkap dalam pertanyaan nomor 3, tanpa, dan sebelum benar-benar cukup mencakup pertanyaan 2, atau pertanyaan 1.

Mengapa Anda membutuhkan monad?

Bahasa fungsional murni seperti Haskell berbeda dari bahasa imperatif seperti C, atau Java dalam hal itu, program fungsional murni tidak harus dijalankan dalam urutan tertentu, satu langkah pada satu waktu. Program Haskell lebih mirip dengan fungsi matematika, di mana Anda dapat menyelesaikan "persamaan" di sejumlah pesanan potensial. Ini memberi sejumlah manfaat, di antaranya adalah menghilangkan kemungkinan jenis bug tertentu, terutama yang berkaitan dengan hal-hal seperti "keadaan".

Namun, ada masalah tertentu yang tidak begitu mudah dipecahkan dengan gaya pemrograman ini. Beberapa hal, seperti pemrograman konsol, dan file i / o, memerlukan hal-hal yang terjadi dalam urutan tertentu, atau perlu mempertahankan keadaan. Salah satu cara untuk mengatasi masalah ini adalah dengan membuat semacam objek yang mewakili keadaan komputasi, dan serangkaian fungsi yang mengambil objek keadaan sebagai input, dan mengembalikan objek keadaan yang dimodifikasi baru.

Jadi mari kita buat nilai "status" hipotetis, yang mewakili kondisi layar konsol. persis bagaimana nilai ini dibangun tidak penting, tetapi katakanlah itu adalah array byte panjang karakter ascii yang mewakili apa yang saat ini terlihat di layar, dan array yang mewakili baris input terakhir yang dimasukkan oleh pengguna, dalam pseudocode. Kami telah menetapkan beberapa fungsi yang mengambil status konsol, memodifikasinya, dan mengembalikan status konsol baru.

consolestate MyConsole = new consolestate;

Jadi untuk melakukan pemrograman konsol, tetapi dengan cara fungsional murni, Anda perlu membuat banyak panggilan fungsi di dalam satu sama lain.

consolestate FinalConsole = print(input(print(myconsole, "Hello, what's your name?")),"hello, %inputbuffer%!");

Pemrograman dengan cara ini menjaga gaya fungsional "murni", sementara memaksa perubahan pada konsol terjadi dalam urutan tertentu. Tetapi, kami mungkin ingin melakukan lebih dari sekedar beberapa operasi pada waktu seperti pada contoh di atas. Fungsi bersarang dengan cara itu akan mulai menjadi canggung. Apa yang kita inginkan, adalah kode yang pada dasarnya melakukan hal yang sama seperti di atas, tetapi ditulis lebih seperti ini:

consolestate FinalConsole = myconsole:
                            print("Hello, what's your name?"):
                            input():
                            print("hello, %inputbuffer%!");

Ini memang akan menjadi cara yang lebih nyaman untuk menulisnya. Bagaimana kita melakukannya?

Apa itu monad?

Setelah Anda memiliki tipe (seperti consolestate) yang Anda tentukan bersama dengan banyak fungsi yang dirancang khusus untuk beroperasi pada tipe itu, Anda dapat mengubah seluruh paket hal-hal ini menjadi "monad" dengan mendefinisikan operator seperti :(bind) yang secara otomatis mengumpankan nilai kembali di sebelah kirinya, ke dalam parameter fungsi di sebelah kanannya, dan liftoperator yang mengubah fungsi normal, menjadi fungsi yang bekerja dengan operator pengikat jenis tertentu itu.

Bagaimana monad diimplementasikan?

Lihat jawaban lain, yang tampaknya cukup bebas untuk melompat ke detail itu.


Sequencing bukan satu-satunya alasan untuk mendefinisikan monad. Monad adalah sembarang fungsi yang mengikat dan kembali. Bind dan kembali memberi Anda urutan. Tetapi mereka memberikan hal-hal lain juga. Juga, perhatikan bahwa bahasa imperatif favorit Anda secara efektif adalah mono IO mewah dengan kelas-kelas OO. Membuatnya mudah untuk mendefinisikan monad berarti mudah untuk menggunakan pola juru bahasa - mendefinisikan dsl sebagai monad dan menafsirkannya!
Nomen


38

Setelah memberikan jawaban untuk pertanyaan ini beberapa tahun yang lalu, saya percaya saya dapat meningkatkan dan menyederhanakan tanggapan itu dengan ...

Monad adalah teknik komposisi fungsi yang mengeksternalkan pengobatan untuk beberapa skenario input menggunakan fungsi penulisan bind, untuk memproses input sebelum proses komposisi.

Dalam komposisi normal, fungsi,, compose (>>)digunakan untuk menerapkan fungsi yang tersusun pada hasil pendahulunya secara berurutan. Yang penting, fungsi yang sedang disusun diperlukan untuk menangani semua skenario inputnya.

(x -> y) >> (y -> z)

Desain ini dapat ditingkatkan dengan merestrukturisasi input sehingga keadaan yang relevan lebih mudah diinterogasi. Jadi, alih-alih hanya ynilai dapat menjadi Mbseperti, misalnya, (is_OK, b)jika ymenyertakan gagasan validitas.

Misalnya, ketika input hanya berupa angka, alih-alih mengembalikan string yang dapat berisi dengan patuh berisi angka atau tidak, Anda bisa merestrukturisasi jenisnya menjadi yang boolmenunjukkan keberadaan nomor yang valid dan angka dalam tupel seperti bool * float,. Fungsi-fungsi yang dikomposisikan sekarang tidak lagi perlu mengurai string input untuk menentukan apakah suatu angka ada tetapi hanya dapat memeriksa boolbagian dari sebuah tuple.

(Ma -> Mb) >> (Mb -> Mc)

Di sini, sekali lagi, komposisi terjadi secara alami composedan karenanya setiap fungsi harus menangani semua skenario inputnya secara individual, meskipun melakukannya sekarang jauh lebih mudah.

Namun, bagaimana jika kita bisa mengeksternalisasi upaya interogasi untuk saat-saat di mana menangani skenario adalah rutin. Sebagai contoh, bagaimana jika program kami tidak melakukan apa-apa saat input tidak OK seperti pada saat is_OKitu false. Jika itu dilakukan maka fungsi yang dikomposisikan tidak perlu menangani skenario itu sendiri, secara dramatis menyederhanakan kode mereka dan memengaruhi tingkat penggunaan kembali yang lain.

Untuk mencapai eksternalisasi ini, kita dapat menggunakan fungsi bind (>>=),, untuk melakukan compositionalih - alih compose. Dengan demikian, alih-alih hanya mentransfer nilai dari output satu fungsi ke input yang lain Bindakan memeriksa Mbagian Madan memutuskan apakah dan bagaimana menerapkan fungsi yang dikomposisikan ke a. Tentu saja, fungsi bindakan didefinisikan secara khusus untuk kita Msehingga dapat memeriksa strukturnya dan melakukan apa pun jenis aplikasi yang kita inginkan. Meskipun demikian, adapat berupa apa saja karena bindhanya meneruskan yang tidak aterinspeksi ke fungsi yang dikomposisikan ketika itu menentukan aplikasi yang diperlukan. Selain itu, fungsi yang dikomposisikan sendiri tidak perlu lagi berurusan denganMsebagian dari struktur input baik, menyederhanakannya. Karenanya...

(a -> Mb) >>= (b -> Mc) atau lebih ringkas Mb >>= (b -> Mc)

Singkatnya, sebuah monad mengeksternalisasi dan dengan demikian memberikan perilaku standar seputar penanganan skenario input tertentu begitu input dirancang untuk mengeksposnya secara memadai. Desain ini adalah shell and contentmodel di mana shell berisi data yang relevan dengan penerapan fungsi yang disusun dan diinterogasi oleh dan tetap hanya tersedia untuk bindfungsi tersebut.

Karena itu, monad adalah tiga hal:

  1. sebuah Mshell untuk menyimpan informasi yang relevan monad,
  2. sebuah bindfungsi yang diimplementasikan untuk memanfaatkan informasi shell ini dalam penerapan fungsi yang dikomposisikan ke nilai konten yang ditemukannya di dalam shell, dan
  3. fungsi yang dapat dikomposisikan dari bentuk a -> Mb,, menghasilkan hasil yang mencakup data manajemen monadik.

Secara umum, input ke suatu fungsi jauh lebih ketat daripada outputnya yang dapat mencakup hal-hal seperti kondisi kesalahan; karenanya, Mbstruktur hasil umumnya sangat bermanfaat. Misalnya, operator divisi tidak mengembalikan nomor ketika pembagi itu 0.

Selain itu, monads dapat mencakup fungsi bungkus yang membungkus nilai,, ake dalam jenis monadik Ma,, dan fungsi umum a -> b,, ke dalam fungsi monadik a -> Mb,, dengan membungkus hasil mereka setelah aplikasi. Tentu saja, seperti bind, fungsi bungkus seperti itu khusus untuk M. Sebuah contoh:

let return a = [a]
let lift f a = return (f a)

Desain bindfungsi mengandaikan struktur data yang tidak dapat diubah dan fungsi murni yang lainnya menjadi kompleks dan jaminan tidak dapat dibuat. Karena itu, ada hukum monadik:

Diberikan ...

M_ 
return = (a -> Ma)
f = (a -> Mb)
g = (b -> Mc)

Kemudian...

Left Identity  : (return a) >>= f === f a
Right Identity : Ma >>= return    === Ma
Associative    : Ma >>= (f >>= g) === Ma >>= ((fun x -> f x) >>= g)

Associativityberarti bindmempertahankan urutan evaluasi terlepas dari kapan bindditerapkan. Artinya, dalam definisi Associativitydi atas, gaya evaluasi awal kurung bindingdari fdan ghanya akan menghasilkan fungsi yang mengharapkan Mauntuk menyelesaikan bind. Oleh karena itu evaluasi Maharus ditentukan sebelum nilainya dapat diterapkan fdan hasilnya pada gilirannya diterapkan g.


"... tapi saya harap orang lain menganggapnya berguna" itu memang berguna bagi saya, terlepas dari semua kalimat yang ditekankan: D

Ini adalah penjelasan monad yang paling singkat dan jelas yang pernah saya baca / tonton / dengar. Terima kasih!
James

Ada perbedaan penting antara Monad dan Monoid. Monad adalah aturan untuk "menyusun" fungsi di antara berbagai jenis, sehingga mereka tidak membentuk operasi biner seperti yang disyaratkan untuk Monoids, lihat di sini untuk detail lebih lanjut: stackoverflow.com/questions/2704652/…
Dmitri Zaitsev

Iya. Anda benar. Artikel Anda di atas kepala saya :). Namun, saya menemukan perawatan ini sangat membantu (dan menambahkannya ke tambang sebagai arahan kepada orang lain). Terima kasih kepala Anda: stackoverflow.com/a/7829607/1612190
George

2
Anda mungkin bingung dengan teori grup Aljabar dengan teori Kategori dari mana Monad berasal. Yang pertama adalah teori kelompok aljabar, yang tidak terkait.
Dmitri Zaitsev

37

Monad adalah, secara efektif, bentuk "operator tipe". Itu akan melakukan tiga hal. Pertama itu akan "membungkus" (atau mengkonversi) nilai dari satu jenis ke jenis lain (biasanya disebut "tipe monadik"). Kedua itu akan membuat semua operasi (atau fungsi) tersedia pada tipe yang mendasarinya tersedia pada tipe monadik. Akhirnya akan memberikan dukungan untuk menggabungkan diri dengan monad lain untuk menghasilkan monad komposit.

"Mungkin monad" pada dasarnya setara dengan "tipe yang dapat dibatalkan" dalam Visual Basic / C #. Dibutuhkan tipe "T" yang tidak dapat dibatalkan dan mengubahnya menjadi "Nullable <T>", dan kemudian mendefinisikan apa arti semua operator biner pada Nullable <T>.

Efek samping terwakili secara bersamaan. Struktur dibuat yang menyimpan deskripsi efek samping di samping nilai pengembalian fungsi. Operasi "terangkat" kemudian menyalin efek samping saat nilai melewati fungsi.

Mereka disebut "monad" daripada nama "operator tipe" yang lebih mudah dipahami karena beberapa alasan:

  1. Monads memiliki batasan pada apa yang dapat mereka lakukan (lihat definisi untuk detailnya).
  2. Batasan-batasan itu, bersama dengan fakta bahwa ada tiga operasi yang terlibat, sesuai dengan struktur dari sesuatu yang disebut monad dalam Kategori Teori, yang merupakan cabang matematika yang tidak jelas.
  3. Mereka dirancang oleh para pendukung bahasa fungsional "murni"
  4. Pendukung bahasa fungsional murni seperti cabang matematika yang tidak jelas
  5. Karena matematika tidak jelas, dan monad dikaitkan dengan gaya pemrograman tertentu, orang cenderung menggunakan kata monad sebagai semacam jabat tangan rahasia. Karena itu tidak ada yang mau repot-repot berinvestasi untuk nama yang lebih baik.

1
Monads tidak 'dirancang', mereka diterapkan dari satu domain (teori kategori) ke yang lain (I / O dalam bahasa pemrograman murni fungsional). Apakah Newton 'merancang' kalkulus?
Jared Updike

1
Butir 1 dan 2 di atas benar dan bermanfaat. Poin 4 dan 5 adalah semacam hominem iklan, meskipun lebih atau kurang benar. Mereka tidak benar-benar membantu menjelaskan monad.
Jared Updike

13
Re: 4, 5: "Jabat tangan rahasia" adalah ikan haring merah. Pemrograman penuh dengan jargon. Haskell kebetulan memanggil barang apa adanya tanpa berpura-pura menemukan kembali sesuatu. Jika sudah ada dalam matematika, mengapa membuat nama baru untuk itu? Nama itu sebenarnya bukan alasan orang tidak mendapatkan monad; mereka adalah konsep yang halus. Rata-rata orang mungkin memahami penambahan dan perkalian, mengapa mereka tidak mendapatkan konsep Grup Abelian? Karena itu lebih abstrak dan umum dan orang itu belum melakukan pekerjaan untuk membungkus kepala mereka di sekitar konsep. Perubahan nama tidak akan membantu.
Jared Updike

16
Sigh ... Aku tidak menyerang Haskell ... Aku membuat lelucon. Jadi, saya tidak terlalu mengerti tentang menjadi "ad hominem". Ya, kalkulus itu "dirancang". Itu sebabnya, misalnya, siswa kalkulus diajari notasi Leibniz, daripada hal-hal menjijikkan yang digunakan Netwton. Desain yang lebih baik. Nama yang baik sangat membantu dalam memahami. Jika saya menyebut Abelian Groups "pod kerutan buncit", Anda mungkin kesulitan memahami saya. Anda mungkin mengatakan "tetapi nama itu tidak masuk akal", tidak ada yang akan menyebut mereka seperti itu. Bagi orang-orang yang belum pernah mendengar teori kategori "monad" terdengar seperti omong kosong.
Scott Wisniewski

4
@Scott: maaf jika komentar saya yang luas membuat saya merasa defensif tentang Haskell. Saya menikmati humor Anda tentang jabat tangan rahasia dan Anda akan perhatikan saya katakan itu kurang lebih benar. :-) Jika Anda menyebut Abelian Groups "pod kerutan buncit", Anda akan membuat kesalahan yang sama dengan mencoba memberi monad "nama yang lebih baik" (lih. F # "ekspresi perhitungan"): istilah itu ada dan orang yang peduli tahu apa yang monad adalah, tetapi bukan apa "hal-hal fuzzy hangat" (atau "ekspresi perhitungan"). Jika saya memahami penggunaan istilah "operator jenis" dengan benar, ada banyak operator jenis lain selain monad.
Jared Updike

35

(Lihat juga jawaban di What is a monad? )

Motivasi yang baik untuk Monads adalah sigfpe (Dan Piponi), You Could Have Invented Monads! (Dan Mungkin Anda Sudah Memiliki) . Ada BANYAK tutorial monad lainnya , banyak di antaranya secara keliru mencoba menjelaskan monad dalam "istilah sederhana" menggunakan berbagai analogi: ini adalah kesalahan tutorial monad ; Hindari mereka.

Seperti yang dikatakan DR MacIver dalam Beri tahu kami mengapa bahasa Anda payah :

Jadi, hal-hal yang saya benci tentang Haskell:

Mari kita mulai dengan yang jelas. Tutorial Monad. Bukan, bukan monad. Khususnya tutorial. Mereka tak ada habisnya, berlebihan, dan tuhan sayang mereka membosankan. Lebih jauh, saya belum pernah melihat bukti meyakinkan bahwa mereka benar-benar membantu. Baca definisi kelas, tulis beberapa kode, dapatkan dari nama menakutkan.

Anda mengatakan Anda mengerti Mungkin monad? Bagus, Anda sedang dalam perjalanan. Mulailah menggunakan monad lain dan cepat atau lambat Anda akan mengerti apa itu monad secara umum.

[Jika Anda berorientasi secara matematis, Anda mungkin ingin mengabaikan lusinan tutorial dan mempelajari definisi, atau mengikuti kuliah dalam teori kategori :) Bagian utama dari definisi adalah bahwa Monad M melibatkan "konstruktor tipe" yang mendefinisikan untuk masing-masing tipe yang ada "T" tipe baru "MT", dan beberapa cara untuk bolak-balik antara tipe "biasa" dan tipe "M".]

Juga, cukup mengejutkan, salah satu pengantar terbaik untuk monad sebenarnya adalah salah satu makalah akademis awal yang memperkenalkan monad, Monad karya Philip Wadler untuk pemrograman fungsional . Ini sebenarnya memiliki contoh-contoh yang praktis dan memotivasi non-sepele , tidak seperti banyak tutorial buatan di luar sana.


2
Satu-satunya masalah dengan makalah Wadler adalah notasi berbeda tetapi saya setuju bahwa makalah ini cukup menarik dan motivasi singkat yang jelas untuk menerapkan monad.
Jared Updike

+1 untuk "kesalahan tutorial monad". Tutorial pada monads mirip dengan beberapa tutorial yang mencoba menjelaskan konsep bilangan bulat. Satu tutorial akan mengatakan, "1 mirip dengan apel"; tutorial lain mengatakan, "2 seperti pir"; yang ketiga mengatakan, "3 pada dasarnya adalah jeruk". Tetapi Anda tidak pernah mendapatkan seluruh gambar dari satu tutorial. Yang saya ambil dari situ adalah bahwa monad adalah konsep abstrak yang dapat digunakan untuk banyak tujuan yang sangat berbeda.
stakx - tidak lagi berkontribusi

@stakx: Ya, benar. Tetapi saya tidak bermaksud bahwa monad adalah sebuah abstraksi yang tidak dapat Anda pelajari atau tidak boleh pelajari; hanya itu yang terbaik untuk mempelajarinya setelah Anda telah melihat cukup banyak contoh nyata untuk merasakan satu abstraksi yang mendasarinya. Lihat jawaban saya yang lain di sini .
ShreevatsaR

5
Kadang-kadang saya merasa bahwa ada begitu banyak tutorial yang mencoba meyakinkan pembaca bahwa monad bermanfaat dengan menggunakan kode yang melakukan hal-hal rumit atau berguna. Itu menghambat pemahaman saya selama berbulan-bulan. Saya tidak belajar seperti itu. Saya lebih suka melihat kode yang sangat sederhana, melakukan sesuatu yang bodoh yang bisa saya lalui secara mental dan saya tidak dapat menemukan contoh seperti ini. Saya tidak bisa belajar jika contoh pertama adalah monad untuk mengurai tata bahasa yang rumit. Saya bisa belajar apakah itu monad untuk menjumlahkan bilangan bulat.
Rafael S. Calsaverini

Menyebutkan tipe konstruktor saja yang tidak lengkap: stackoverflow.com/a/37345315/1614973
Dmitri Zaitsev

23

Monads adalah untuk mengontrol aliran apa tipe data abstrak untuk data.

Dengan kata lain, banyak pengembang merasa nyaman dengan gagasan Set, Daftar, Kamus (atau Hash, atau Maps), dan Trees. Di dalam tipe data tersebut ada banyak kasus khusus (misalnya InsertionOrderPreservingIdentityHashMap).

Namun, ketika dihadapkan dengan "aliran" program banyak pengembang belum terkena lebih banyak konstruksi daripada jika, beralih / kasing, lakukan, sementara, goto (grr), dan (mungkin) penutupan.

Jadi, monad hanyalah sebuah konstruksi aliran kontrol. Ungkapan yang lebih baik untuk mengganti monad adalah 'tipe kontrol'.

Dengan demikian, sebuah monad memiliki slot untuk logika kontrol, atau pernyataan, atau fungsi - padanan dalam struktur data akan mengatakan bahwa beberapa struktur data memungkinkan Anda untuk menambahkan data, dan menghapusnya.

Misalnya, monad "jika":

if( clause ) then block

paling sederhana memiliki dua slot - klausa, dan satu blok. The ifmonad biasanya dibangun untuk mengevaluasi hasil dari klausa, dan jika tidak salah, mengevaluasi blok. Banyak pengembang tidak diperkenalkan dengan monad ketika mereka belajar 'jika', dan tidak perlu memahami monad untuk menulis logika yang efektif.

Monads dapat menjadi lebih rumit, dengan cara yang sama seperti struktur data dapat menjadi lebih rumit, tetapi ada banyak kategori luas monad yang mungkin memiliki semantik yang serupa, tetapi implementasi dan sintaksis berbeda.

Tentu saja, dengan cara yang sama bahwa struktur data dapat diulang, atau dilalui, monad dapat dievaluasi.

Compiler mungkin atau mungkin tidak memiliki dukungan untuk monad yang ditentukan pengguna. Haskell tentu saja melakukannya. Ioke memiliki beberapa kemampuan yang serupa, meskipun istilah monad tidak digunakan dalam bahasa tersebut.


14

Tutorial Monad favorit saya:

http://www.haskell.org/haskellwiki/All_About_Monads

(dari 170.000 klik pada pencarian Google untuk "tutorial monad"!)

@ Sat: Titik monad adalah untuk memungkinkan Anda untuk menambahkan (biasanya) semantik berurutan ke kode murni; Anda bahkan dapat membuat monad (menggunakan Monad Transformers) dan mendapatkan semantik gabungan yang lebih menarik dan rumit, seperti parsing dengan penanganan kesalahan, keadaan bersama, dan pencatatan, misalnya. Semua ini dimungkinkan dalam kode murni, monads hanya memungkinkan Anda untuk abstrak itu dan menggunakannya kembali di perpustakaan modular (selalu baik dalam pemrograman), serta memberikan sintaks yang mudah agar terlihat penting.

Haskell sudah mempunyai operator overloading [1]: ia menggunakan kelas tipe seperti halnya orang menggunakan antarmuka di Java atau C # tetapi Haskell kebetulan juga memperbolehkan token non-alfanumerik seperti + && dan> sebagai pengidentifikasi infiks. Ini hanya kelebihan operator dengan cara Anda melihatnya jika Anda bermaksud "membebani titik koma" [2]. Kedengarannya seperti ilmu hitam dan meminta masalah untuk "membebani titik koma" (peretas giat Perl mendapatkan ide ini) tetapi intinya adalah bahwa tanpa monad tidak ada tanda titik koma, karena kode fungsional murni tidak memerlukan atau memungkinkan sequencing eksplisit.

Ini semua terdengar jauh lebih rumit dari yang seharusnya. Artikel sigfpe cukup keren tetapi menggunakan Haskell untuk menjelaskannya, jenis kegagalan untuk memecahkan masalah ayam dan telur dalam memahami Haskell untuk grok Monads dan memahami Monads untuk grok Haskell.

[1] Ini adalah masalah terpisah dari monad tetapi monad menggunakan fitur kelebihan operator Haskell.

[2] Ini juga merupakan penyederhanaan yang berlebihan karena operator untuk merantai aksi monadik adalah >> = (diucapkan "mengikat") tetapi ada gula sintaksis ("lakukan") yang memungkinkan Anda menggunakan kawat gigi dan titik koma dan / atau lekukan dan baris baru.


9

Saya telah memikirkan Monads dengan cara yang berbeda, akhir-akhir ini. Saya telah memikirkan mereka sebagai abstrak urutan eksekusi dengan cara matematika, yang memungkinkan jenis polimorfisme baru menjadi mungkin.

Jika Anda menggunakan bahasa imperatif, dan Anda menulis beberapa ekspresi secara berurutan, kode SELALU berjalan persis dalam urutan itu.

Dan dalam kasus sederhana, ketika Anda menggunakan monad, rasanya sama - Anda mendefinisikan daftar ekspresi yang terjadi secara berurutan. Kecuali itu, tergantung pada monad mana yang Anda gunakan, kode Anda mungkin berjalan secara berurutan (seperti di IO monad), secara paralel atas beberapa item sekaligus (seperti dalam Daftar monad), mungkin berhenti di tengah jalan (seperti di Mungkin monad) , mungkin berhenti di tengah jalan untuk dilanjutkan kembali nanti (seperti dalam monad Pengembalian), ia mungkin mundur dan mulai dari awal (seperti dalam monad Transaksi), atau mungkin mundur sebagian untuk mencoba opsi lain (seperti dalam Logika monad) .

Dan karena monad bersifat polimorfik, Anda dapat menjalankan kode yang sama di monad yang berbeda, tergantung kebutuhan Anda.

Plus, dalam beberapa kasus, dimungkinkan untuk menggabungkan monad bersama-sama (dengan transformer monad) untuk mendapatkan beberapa fitur sekaligus.


9

Saya masih baru di monad, tetapi saya pikir saya akan membagikan tautan yang menurut saya terasa sangat enak dibaca (DENGAN GAMBAR !!): http://www.matusiak.eu/numerodix/blog/2012/3/11/ monads-for-the-awam / (tidak ada afiliasi)

Pada dasarnya, konsep hangat dan tidak jelas yang saya dapatkan dari artikel ini adalah konsep bahwa monad pada dasarnya adalah adaptor yang memungkinkan fungsi yang berbeda untuk bekerja dengan cara yang dapat digubah, yaitu dapat merangkai beberapa fungsi dan mencampur dan mencocokkannya tanpa khawatir tentang pengembalian yang tidak konsisten. jenis dan semacamnya. Jadi fungsi BIND bertugas menjaga apel dengan apel dan jeruk dengan jeruk ketika kita mencoba membuat adaptor ini. Dan fungsi LIFT bertugas mengambil fungsi "level bawah" dan "memutakhirkan" mereka untuk bekerja dengan fungsi BIND dan dapat dikomposasikan juga.

Saya harap saya melakukannya dengan benar, dan yang lebih penting, berharap artikel tersebut memiliki pandangan yang valid tentang monad. Jika tidak ada yang lain, artikel ini membantu membangkitkan selera saya untuk belajar lebih banyak tentang Monad.


Contoh python membuatnya mudah untuk dipahami! Terima kasih telah berbagi.
Ryan Efendy

8

Selain jawaban yang sangat baik di atas, izinkan saya menawarkan Anda tautan ke artikel berikut (oleh Patrick Thomson) yang menjelaskan monad dengan menghubungkan konsep ke pustaka JavaScript jQuery (dan caranya menggunakan "metode perangkaian" untuk memanipulasi DOM) : jQuery adalah Monad

The jQuery dokumentasi sendiri tidak merujuk pada istilah "monad" tapi berbicara tentang "pola pembangun" yang mungkin lebih akrab. Ini tidak mengubah fakta bahwa Anda memiliki monad yang tepat di sana mungkin bahkan tanpa menyadarinya.


Jika Anda menggunakan jQuery, penjelasan ini bisa sangat membantu, terutama jika Haskell Anda tidak kuat
byteclub

10
JQuery dengan tegas bukan monad. Artikel yang ditautkan salah.
Tony Morris

1
Menjadi "empatik" tidak terlalu meyakinkan. Untuk beberapa diskusi bermanfaat tentang topik ini, lihat Apakah jQuery a monad - Stack Overflow
nealmcb

1
Lihat juga Douglas Crackford ini Google Talk monads dan Gonad dan kode Javascript nya untuk melakukan modads, memperluas perilaku serupa perpustakaan AJAX dan Janji: douglascrockford / monad · GitHub
nealmcb


7

Monad adalah cara menggabungkan komputasi bersama yang berbagi konteks umum. Itu seperti membangun jaringan pipa. Saat membangun jaringan, tidak ada data yang mengalir melaluinya. Tetapi ketika saya telah selesai mengumpulkan semua bit bersama dengan 'bind' dan 'return' maka saya memanggil sesuatu seperti runMyMonad monad datadan data mengalir melalui pipa.


1
Itu lebih mirip Aplikatif daripada Monad. Dengan Monads, Anda harus mendapatkan data dari pipa sebelum Anda dapat memilih pipa berikutnya untuk dihubungkan.
Peaker

ya, Anda menggambarkan Aplikatif, bukan Monad. Monad adalah, membangun segmen pipa berikutnya di tempat, tergantung pada data yang mencapai titik itu, di dalam pipa.
Will Ness

6

Dalam praktiknya, monad adalah implementasi kustom dari operator komposisi fungsi yang menangani efek samping dan nilai input dan return yang tidak kompatibel (untuk rantai).



5

Dua hal yang paling membantu saya ketika belajar tentang itu adalah:

Bab 8, "Parsing Fungsional," dari buku Graham Hutton Programming in Haskell . Ini tidak menyebutkan monad sama sekali, sebenarnya, tetapi jika Anda dapat membaca bab dan benar-benar memahami segalanya di dalamnya, terutama bagaimana urutan operasi pengikatan dievaluasi, Anda akan memahami internal monad. Harapkan ini untuk mengambil beberapa percobaan.

Tutorial Semua Tentang Monads . Ini memberikan beberapa contoh bagus penggunaannya, dan saya harus mengatakan bahwa analogi dalam Lampiran saya bekerja untuk saya.


5

Monoid tampaknya menjadi sesuatu yang memastikan bahwa semua operasi yang ditentukan pada Monoid dan tipe yang didukung akan selalu mengembalikan tipe yang didukung di dalam Monoid. Misalnya, Semua nomor + Semua nomor = Sejumlah, tidak ada kesalahan.

Sedangkan pembagian menerima dua pecahan, dan mengembalikan pecahan, yang mendefinisikan pembagian dengan nol sebagai Infinity dalam beberapa haskell (yang kebetulan merupakan pecahan pecahan) ...

Bagaimanapun, tampaknya Monads hanyalah cara untuk memastikan bahwa rangkaian operasi Anda berperilaku dengan cara yang dapat diprediksi, dan fungsi yang mengklaim sebagai Num -> Num, yang disusun dengan fungsi lain dari Num-> Num yang dipanggil dengan x tidak katakanlah, tembak rudal.

Di sisi lain, jika kita memiliki fungsi yang menembakkan rudal, kita dapat menyusunnya dengan fungsi lain yang juga menembakkan rudal, karena maksud kita jelas - kita ingin menembakkan rudal - tetapi tidak akan mencoba mencetak "Hello World" untuk beberapa alasan aneh.

Dalam Haskell, utama adalah tipe IO (), atau IO [()], distensinya aneh dan saya tidak akan membahasnya tetapi inilah yang saya pikir terjadi:

Jika saya punya main, saya ingin melakukan serangkaian tindakan, alasan saya menjalankan program adalah untuk menghasilkan efek - biasanya melalui IO. Dengan demikian saya dapat rantai operasi IO bersama-sama di utama untuk melakukan IO, tidak ada yang lain.

Jika saya mencoba melakukan sesuatu yang tidak "mengembalikan IO", program akan mengeluh bahwa rantai tidak mengalir, atau pada dasarnya "Bagaimana ini berhubungan dengan apa yang kita coba lakukan - tindakan IO", tampaknya memaksa programmer untuk terus melatih pemikiran mereka, tanpa menyimpang dan berpikir tentang menembakkan rudal, sambil membuat algoritma untuk menyortir - yang tidak mengalir.

Pada dasarnya, Monads tampaknya menjadi tip bagi kompiler bahwa "hei, Anda tahu fungsi ini yang mengembalikan angka di sini, itu sebenarnya tidak selalu berfungsi, kadang-kadang dapat menghasilkan Angka, dan kadang-kadang Tidak ada sama sekali, simpan saja ini di pikiran". Mengetahui hal ini, jika Anda mencoba untuk menegaskan tindakan monadik, tindakan monadik dapat bertindak sebagai pengecualian waktu kompilasi yang mengatakan "hei, ini sebenarnya bukan angka, ini BISA angka, tetapi Anda tidak dapat menganggap ini, lakukan sesuatu untuk memastikan bahwa alirannya dapat diterima. " yang mencegah perilaku program yang tidak terduga - sampai batas tertentu.

Tampaknya monad bukan tentang kemurnian, atau kontrol, tetapi tentang mempertahankan identitas kategori di mana semua perilaku dapat diprediksi dan didefinisikan, atau tidak dikompilasi. Anda tidak dapat melakukan apa-apa ketika Anda diharapkan untuk melakukan sesuatu, dan Anda tidak dapat melakukan sesuatu jika Anda diharapkan untuk tidak melakukan apa-apa (terlihat).

Alasan terbesar yang dapat saya pikirkan untuk Monads adalah - lihat kode Prosedural / OOP, dan Anda akan melihat bahwa Anda tidak tahu di mana program dimulai, atau berakhir, yang Anda lihat adalah banyak lompatan dan banyak matematika , sihir, dan rudal. Anda tidak akan dapat memeliharanya, dan jika Anda bisa, Anda akan menghabiskan cukup banyak waktu untuk menyelami seluruh program sebelum Anda dapat memahami bagian mana pun darinya, karena modularitas dalam konteks ini didasarkan pada "bagian" yang saling tergantung. kode, di mana kode dioptimalkan untuk menjadi terkait mungkin untuk janji efisiensi / antar-hubungan. Monad sangat konkret, dan didefinisikan dengan baik oleh definisi, dan memastikan bahwa aliran program dimungkinkan untuk dianalisis, dan mengisolasi bagian-bagian yang sulit dianalisis - karena mereka sendiri adalah monad. Monad tampaknya menjadi " atau menghancurkan alam semesta atau bahkan mengubah waktu - kita tidak tahu atau tidak memiliki jaminan bahwa ITU ADALAH ITU. A monad MENJAMIN BAHWA ITU ADALAH APA ITU. yang sangat kuat. atau menghancurkan alam semesta atau bahkan mengubah waktu - kita tidak tahu atau tidak memiliki jaminan bahwa ITU ADALAH ITU. A monad MENJAMIN BAHWA ITU ADALAH APA ITU. yang sangat kuat.

Semua hal di "dunia nyata" tampaknya adalah monad, dalam arti bahwa ia terikat oleh hukum yang dapat diobservasi secara pasti yang mencegah kebingungan. Ini tidak berarti kita harus meniru semua operasi objek ini untuk membuat kelas, sebaliknya kita dapat dengan mudah mengatakan "kotak adalah kotak", hanya kotak, bahkan persegi panjang atau lingkaran, dan "persegi memiliki luas dari panjang salah satu dimensi yang ada dikalikan dengan sendirinya. Tidak peduli apa persegi yang Anda miliki, jika itu persegi dalam ruang 2D, luasnya sama sekali tidak bisa apa-apa selain panjangnya kuadrat, hampir sepele untuk dibuktikan. Ini sangat kuat karena kita tidak perlu membuat pernyataan untuk memastikan bahwa dunia kita sebagaimana adanya, kita hanya menggunakan implikasi realitas untuk mencegah program-program kita dari jalur yang salah.

Saya cukup dijamin salah, tapi saya pikir ini bisa membantu seseorang di luar sana, jadi semoga membantu seseorang.


5

Dalam konteks Scala, Anda akan menemukan yang berikut ini sebagai definisi paling sederhana. Pada dasarnya flatMap (atau bind) adalah 'asosiatif' dan ada identitas.

trait M[+A] {
  def flatMap[B](f: A => M[B]): M[B] // AKA bind

  // Pseudo Meta Code
  def isValidMonad: Boolean = {
    // for every parameter the following holds
    def isAssociativeOn[X, Y, Z](x: M[X], f: X => M[Y], g: Y => M[Z]): Boolean =
      x.flatMap(f).flatMap(g) == x.flatMap(f(_).flatMap(g))

    // for every parameter X and x, there exists an id
    // such that the following holds
    def isAnIdentity[X](x: M[X], id: X => M[X]): Boolean =
      x.flatMap(id) == x
  }
}

Misalnya

// These could be any functions
val f: Int => Option[String] = number => if (number == 7) Some("hello") else None
val g: String => Option[Double] = string => Some(3.14)

// Observe these are identical. Since Option is a Monad 
// they will always be identical no matter what the functions are
scala> Some(7).flatMap(f).flatMap(g)
res211: Option[Double] = Some(3.14)

scala> Some(7).flatMap(f(_).flatMap(g))
res212: Option[Double] = Some(3.14)


// As Option is a Monad, there exists an identity:
val id: Int => Option[Int] = x => Some(x)

// Observe these are identical
scala> Some(7).flatMap(id)
res213: Option[Int] = Some(7)

scala> Some(7)
res214: Some[Int] = Some(7)

CATATAN Secara tegas definisi Monad dalam pemrograman fungsional tidak sama dengan definisi Monad dalam Kategori Teori , yang didefinisikan secara bergantian mapdan flatten. Meskipun mereka setara di bawah pemetaan tertentu. Presentasi ini sangat bagus: http://www.slideshare.net/samthemonad/monad-presentation-scala-as-a-category


5

Jawaban ini dimulai dengan contoh yang memotivasi, bekerja melalui contoh itu, mendapatkan contoh monad, dan secara resmi mendefinisikan "monad".

Pertimbangkan ketiga fungsi ini dalam pseudocode:

f(<x, messages>) := <x, messages "called f. ">
g(<x, messages>) := <x, messages "called g. ">
wrap(x)          := <x, "">

fmengambil pasangan yang dipesan dari formulir <x, messages>dan mengembalikan pasangan yang dipesan. Ini membuat item pertama tidak tersentuh dan ditambahkan "called f. "ke item kedua. Sama dengan g.

Anda dapat menyusun fungsi-fungsi ini dan mendapatkan nilai asli Anda, bersama dengan string yang menunjukkan urutan fungsi dipanggil:

  f(g(wrap(x)))
= f(g(<x, "">))
= f(<x, "called g. ">)
= <x, "called g. called f. ">

Anda tidak menyukai kenyataan itu fdan gbertanggung jawab untuk menambahkan pesan log mereka sendiri ke informasi logging sebelumnya. (Bayangkan saja demi argumen bahwa alih-alih menambahkan string, fdang harus melakukan logika rumit pada item kedua dari pasangan. Akan merepotkan untuk mengulangi logika rumit itu dalam dua - atau lebih - fungsi yang berbeda.)

Anda lebih suka menulis fungsi yang lebih sederhana:

f(x)    := <x, "called f. ">
g(x)    := <x, "called g. ">
wrap(x) := <x, "">

Tetapi lihat apa yang terjadi ketika Anda menyusunnya:

  f(g(wrap(x)))
= f(g(<x, "">))
= f(<<x, "">, "called g. ">)
= <<<x, "">, "called g. ">, "called f. ">

Masalahnya adalah bahwa melewatkan pasangan ke suatu fungsi tidak memberi Anda apa yang Anda inginkan. Tetapi bagaimana jika Anda bisa memberi makan pasangan ke suatu fungsi:

  feed(f, feed(g, wrap(x)))
= feed(f, feed(g, <x, "">))
= feed(f, <x, "called g. ">)
= <x, "called g. called f. ">

Baca feed(f, m)sebagai "umpan mke f". Untuk pakan pasangan <x, messages>ke dalam fungsi fadalah untuk lulus x dalam f, dapatkan <y, message>keluar dari f, dan kembali <y, messages message>.

feed(f, <x, messages>) := let <y, message> = f(x)
                          in  <y, messages message>

Perhatikan apa yang terjadi ketika Anda melakukan tiga hal dengan fungsi Anda:

Pertama: jika Anda membungkus nilai dan kemudian memberi makan pasangan yang dihasilkan ke fungsi:

  feed(f, wrap(x))
= feed(f, <x, "">)
= let <y, message> = f(x)
  in  <y, "" message>
= let <y, message> = <x, "called f. ">
  in  <y, "" message>
= <x, "" "called f. ">
= <x, "called f. ">
= f(x)

Itu sama dengan melewatkan nilai ke fungsi.

Kedua: jika Anda memasukkan pasangan ke wrap:

  feed(wrap, <x, messages>)
= let <y, message> = wrap(x)
  in  <y, messages message>
= let <y, message> = <x, "">
  in  <y, messages message>
= <x, messages "">
= <x, messages>

Itu tidak mengubah pasangan.

Ketiga: jika Anda mendefinisikan fungsi yang mengambil xdan g(x)memasukkan f:

h(x) := feed(f, g(x))

dan beri makan sepasang ke dalamnya:

  feed(h, <x, messages>)
= let <y, message> = h(x)
  in  <y, messages message>
= let <y, message> = feed(f, g(x))
  in  <y, messages message>
= let <y, message> = feed(f, <x, "called g. ">)
  in  <y, messages message>
= let <y, message> = let <z, msg> = f(x)
                     in  <z, "called g. " msg>
  in <y, messages message>
= let <y, message> = let <z, msg> = <x, "called f. ">
                     in  <z, "called g. " msg>
  in <y, messages message>
= let <y, message> = <x, "called g. " "called f. ">
  in <y, messages message>
= <x, messages "called g. " "called f. ">
= feed(f, <x, messages "called g. ">)
= feed(f, feed(g, <x, messages>))

Itu sama dengan memberi makan pasangan ke gdan memberi makan pasangan yang dihasilkan ke dalam f.

Anda memiliki sebagian besar monad. Sekarang Anda hanya perlu tahu tentang tipe data dalam program Anda.

Apa jenis nilai itu <x, "called f. ">? Yah, itu tergantung pada jenis nilai xapa. Jika xtipe t, maka pasangan Anda adalah nilai tipe "pair of tand string". Panggil tipe itu M t.

Madalah konstruktor tipe: Msendirian tidak merujuk ke tipe, tetapi M _merujuk ke tipe setelah Anda mengisi kosong dengan tipe. An M intadalah sepasang int dan string. An M stringadalah sepasang string dan string. Dll

Selamat, Anda telah membuat monad!

Secara formal, monad Anda adalah tuple <M, feed, wrap>.

Monad adalah tuple di <M, feed, wrap>mana:

  • M adalah tipe konstruktor.
  • feedmengambil (fungsi yang mengambil a tdan mengembalikan sebuah M u) dan an M tdan mengembalikan sebuahM u .
  • wrapmengambil vdan mengembalikan sebuah M v.

t,, udan vtiga jenis yang mungkin sama atau tidak sama. Monad memenuhi tiga properti yang Anda buktikan untuk monad spesifik Anda:

  • Memberi makan yang dibungkus tdengan suatu fungsi sama dengan melewatkan yang tidak dibungkus tke dalam fungsi.

    Secara formal: feed(f, wrap(x)) = f(x)

  • Memberi makan M tke dalam wraptidak ada hubungannya dengan M t.

    Secara formal: feed(wrap, m) = m

  • Mengumpankan M t(sebut saja m) ke fungsi itu

    • melewati tke dalamg
    • mendapat M u(sebut saja n) darig
    • diumpankan nkef

    sama dengan

    • makanan mkeg
    • dapatkan ndarig
    • makan nkef

    Secara formal: di feed(h, m) = feed(f, feed(g, m))manah(x) := feed(f, g(x))

Biasanya, feeddisebut bind(AKA >>=di Haskell) dan wrapdisebut return.


5

Saya akan mencoba menjelaskan Monaddalam konteks Haskell.

Dalam pemrograman fungsional, komposisi fungsi penting. Ini memungkinkan program kami terdiri dari fungsi kecil dan mudah dibaca.

Katakanlah kita memiliki dua fungsi: g :: Int -> Stringdanf :: String -> Bool .

Kita dapat melakukan (f . g) x, yang sama dengan f (g x), di mana xadalahInt nilai.

Ketika melakukan komposisi / menerapkan hasil dari satu fungsi ke fungsi lain, memiliki jenis yang cocok adalah penting. Dalam kasus di atas, jenis hasil yang dikembalikan oleh gharus sama dengan jenis yang diterima olehf .

Tetapi kadang-kadang nilai berada dalam konteks, dan ini membuatnya sedikit lebih mudah untuk berbaris jenis. (Memiliki nilai dalam konteks sangat berguna. Misalnya, Maybe Inttipe tersebut merepresentasikan Intnilai yang mungkin tidak ada, IO Stringtipe tersebut mewakili Stringnilai yang ada sebagai hasil dari melakukan beberapa efek samping.)

Katakanlah sekarang kita punya g1 :: Int -> Maybe Stringdan f1 :: String -> Maybe Bool. g1dan f1sangat mirip dengan gdanf masing masing.

Kita tidak bisa melakukan (f1 . g1) xatau f1 (g1 x), di mana xmerupakan Intnilai. Jenis hasil yang dikembalikan g1bukan apaf1 diharapkan.

Kita bisa menulis fdan gdengan .operator, tetapi sekarang kita tidak bisa menulis f1dan g1dengan .. Masalahnya adalah bahwa kita tidak bisa langsung meneruskan nilai dalam konteks ke fungsi yang mengharapkan nilai yang tidak ada dalam konteks.

Bukankah lebih baik jika kita memperkenalkan operator untuk menulis g1dan f1, sehingga kita bisa menulis (f1 OPERATOR g1) x? g1mengembalikan nilai dalam konteks. Nilai akan diambil di luar konteks dan diterapkan ke f1. Dan ya, kami memiliki operator seperti itu. Nya<=< .

Kami juga punya >>= operator yang melakukan hal yang sama persis bagi kami, meskipun dalam sintaks yang sedikit berbeda.

Kita menulis: g1 x >>= f1. g1 xadalah sebuah Maybe Intnilai. The >>=Operator membantu mengambil Intnilai dari "mungkin-tidak-ada" konteks, dan menerapkannya ke f1. Hasil f1, yang merupakan Maybe Bool, akan menjadi hasil dari seluruh >>=operasi.

Dan akhirnya, mengapa Monadbermanfaat? Karena Monadadalah tipe kelas yang mendefinisikan >>=operator, sangat mirip dengan Eqkelas tipe yang mendefinisikan ==dan /=operator.

Untuk menyimpulkan, Monadkelas tipe mendefinisikan >>=operator yang memungkinkan kami untuk melewatkan nilai dalam konteks (kami menyebutnya nilai monadik) ke fungsi yang tidak mengharapkan nilai dalam konteks. Konteks akan dijaga.

Jika ada satu hal yang perlu diingat di sini, itu Monadmemungkinkan komposisi fungsi yang melibatkan nilai-nilai dalam konteks .



TKI, Monad adalah protokol panggilan fungsi umum.
Will Ness

Jawaban Anda adalah yang paling membantu menurut saya. Meskipun saya harus mengatakan bahwa saya pikir penekanannya perlu pada fakta bahwa fungsi yang Anda maksud tidak hanya melibatkan nilai dalam konteks, mereka secara aktif menempatkan nilai dalam konteks. Jadi misalnya, suatu fungsi, f :: ma -> mb akan dengan mudah menulis dengan fungsi lain, g :: mb -> m c. Tetapi monads (secara spesifik mengikat) memungkinkan kita untuk terus-menerus menyusun fungsi yang menempatkan input mereka dalam konteks yang sama, tanpa kita perlu mengambil nilai dari konteks itu terlebih dahulu (yang secara efektif akan menghapus informasi dari nilai)
James

@ James, saya pikir itu harus menjadi penekanan untuk functors?
Jonas

@Jonas Saya kira saya tidak menjelaskan dengan benar. Ketika saya mengatakan bahwa fungsi menempatkan nilai dalam konteks, maksud saya bahwa mereka memiliki tipe (a -> mb). Ini sangat berguna karena menempatkan nilai ke dalam konteks menambahkan informasi baru ke dalamnya tetapi biasanya akan sulit untuk rantai a (a -> mb) dan a (b -> mc) bersama karena kita tidak bisa hanya mengambil nilai keluar konteksnya. Jadi kita harus menggunakan beberapa proses berbelit-belit untuk rantai fungsi-fungsi ini bersama-sama dalam cara yang masuk akal tergantung pada konteks spesifik dan monad hanya memungkinkan kita untuk melakukan ini secara konsisten, terlepas dari konteksnya.
James

5

tl; dr

{-# LANGUAGE InstanceSigs #-}

newtype Id t = Id t

instance Monad Id where
   return :: t -> Id t
   return = Id

   (=<<) :: (a -> Id b) -> Id a -> Id b
   f =<< (Id x) = f x

Prolog

Operator aplikasi $fungsi

forall a b. a -> b

didefinisikan secara kanonik

($) :: (a -> b) -> a -> b
f $ x = f x

infixr 0 $

dalam hal aplikasi fungsi Haskell-primitif f x( infixl 10).

Komposisi .didefinisikan $sebagai

(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \ x -> f $ g x

infixr 9 .

dan memenuhi persamaan forall f g h.

     f . id  =  f            :: c -> d   Right identity
     id . g  =  g            :: b -> c   Left identity
(f . g) . h  =  f . (g . h)  :: a -> d   Associativity

.bersifat asosiatif, dan idmerupakan identitas kanan dan kirinya.

Triple Kleisli

Dalam pemrograman, monad adalah konstruktor tipe functor dengan turunan dari kelas tipe monad. Ada beberapa varian definisi dan implementasi yang setara, masing-masing membawa intuisi yang sedikit berbeda tentang abstraksi monad.

Sebuah functor adalah konstruktor ftipe * -> *dengan instance kelas tipe functor.

{-# LANGUAGE KindSignatures #-}

class Functor (f :: * -> *) where
   map :: (a -> b) -> (f a -> f b)

Selain mengikuti protokol tipe yang ditegakkan secara statis, instance dari kelas tipe functor harus mematuhi hukum functor aljabar forall f g.

       map id  =  id           :: f t -> f t   Identity
map f . map g  =  map (f . g)  :: f a -> f c   Composition / short cut fusion

Functor perhitungan memiliki jenis

forall f t. Functor f => f t

Suatu perhitungan c rterdiri dari hasil r dalam konteks c .

Fungsi monadik unary atau panah Kleisli memiliki tipe

forall m a b. Functor m => a -> m b

Panah Kleisi adalah fungsi yang mengambil satu argumen adan mengembalikan perhitungan monadik m b.

Monad secara kanonik didefinisikan dalam istilah triple Kleisli forall m. Functor m =>

(m, return, (=<<))

diimplementasikan sebagai kelas tipe

class Functor m => Monad m where
   return :: t -> m t
   (=<<)  :: (a -> m b) -> m a -> m b

infixr 1 =<<

The Kleisli identitas return adalah panah Kleisli yang mempromosikan nilai tdalam konteks monadik m. Aplikasi ekstensi atau Kleisli =<< menerapkan panah Kleisli a -> m bke hasil perhitungan m a.

Komposisi Kleisli <=< didefinisikan dalam hal ekstensi sebagai

(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c)
f <=< g = \ x -> f =<< g x

infixr 1 <=<

<=< menyusun dua panah Kleisli, menerapkan panah kiri ke hasil aplikasi panah kanan.

Contoh dari kelas tipe monad harus mematuhi hukum monad , yang paling elegan dinyatakan dalam komposisi Kleisli:forall f g h.

   f <=< return  =  f                :: c -> m d   Right identity
   return <=< g  =  g                :: b -> m c   Left identity
(f <=< g) <=< h  =  f <=< (g <=< h)  :: a -> m d   Associativity

<=<bersifat asosiatif, dan returnmerupakan identitas kanan dan kirinya.

Identitas

Tipe identitas

type Id t = t

adalah fungsi identitas pada tipe

Id :: * -> *

Ditafsirkan sebagai functor,

   return :: t -> Id t
=      id :: t ->    t

    (=<<) :: (a -> Id b) -> Id a -> Id b
=     ($) :: (a ->    b) ->    a ->    b

    (<=<) :: (b -> Id c) -> (a -> Id b) -> (a -> Id c)
=     (.) :: (b ->    c) -> (a ->    b) -> (a ->    c)

Dalam Haskell kanonik, identitas monad didefinisikan

newtype Id t = Id t

instance Functor Id where
   map :: (a -> b) -> Id a -> Id b
   map f (Id x) = Id (f x)

instance Monad Id where
   return :: t -> Id t
   return = Id

   (=<<) :: (a -> Id b) -> Id a -> Id b
   f =<< (Id x) = f x

Pilihan

Jenis opsi

data Maybe t = Nothing | Just t

mengkodekan perhitungan Maybe tyang tidak selalu menghasilkan hasil t, perhitungan yang mungkin "gagal". Opsi monad ditentukan

instance Functor Maybe where
   map :: (a -> b) -> (Maybe a -> Maybe b)
   map f (Just x) = Just (f x)
   map _ Nothing  = Nothing

instance Monad Maybe where
   return :: t -> Maybe t
   return = Just

   (=<<) :: (a -> Maybe b) -> Maybe a -> Maybe b
   f =<< (Just x) = f x
   _ =<< Nothing  = Nothing

a -> Maybe bditerapkan pada hasil hanya jika Maybe amenghasilkan suatu hasil.

newtype Nat = Nat Int

Bilangan alami dapat dikodekan sebagai bilangan bulat yang lebih besar dari atau sama dengan nol.

toNat :: Int -> Maybe Nat
toNat i | i >= 0    = Just (Nat i)
        | otherwise = Nothing

Bilangan alami tidak ditutup dengan pengurangan.

(-?) :: Nat -> Nat -> Maybe Nat
(Nat n) -? (Nat m) = toNat (n - m)

infixl 6 -?

Opsi monad mencakup bentuk dasar penanganan pengecualian.

(-? 20) <=< toNat :: Int -> Maybe Nat

Daftar

Daftar monad, lebih dari tipe daftar

data [] t = [] | t : [t]

infixr 5 :

dan operasi tambahan “monoid” tambahannya

(++) :: [t] -> [t] -> [t]
(x : xs) ++ ys = x : xs ++ ys
[]       ++ ys = ys

infixr 5 ++

mengkodekan perhitungan nonlinier[t] menghasilkan jumlah 0, 1, ...hasil yang alami t.

instance Functor [] where
   map :: (a -> b) -> ([a] -> [b])
   map f (x : xs) = f x : map f xs
   map _ []       = []

instance Monad [] where
   return :: t -> [t]
   return = (: [])

   (=<<) :: (a -> [b]) -> [a] -> [b]
   f =<< (x : xs) = f x ++ (f =<< xs)
   _ =<< []       = []

Ekstensi =<<menggabungkan ++semua daftar yang [b]dihasilkan dari aplikasi f xpanah Kleisli a -> [b]hingga elemen [a]ke dalam daftar hasil tunggal [b].

Biarkan pembagi yang tepat dari bilangan bulat positif nmenjadi

divisors :: Integral t => t -> [t]
divisors n = filter (`divides` n) [2 .. n - 1]

divides :: Integral t => t -> t -> Bool
(`divides` n) = (== 0) . (n `rem`)

kemudian

forall n.  let { f = f <=< divisors } in f n   =   []

Dalam mendefinisikan kelas tipe monad, alih-alih ekstensi =<<, standar Haskell menggunakan flip, operator bind>>= .

class Applicative m => Monad m where
   (>>=) :: forall a b. m a -> (a -> m b) -> m b

   (>>) :: forall a b. m a -> m b -> m b
   m >> k = m >>= \ _ -> k
   {-# INLINE (>>) #-}

   return :: a -> m a
   return = pure

Demi kesederhanaan, penjelasan ini menggunakan hirarki kelas tipe

class              Functor f
class Functor m => Monad m

Di Haskell, hirarki standar saat ini adalah

class                  Functor f
class Functor p     => Applicative p
class Applicative m => Monad m

karena tidak hanya setiap monad adalah functor, tetapi setiap aplikasi adalah functor dan setiap monad adalah aplikasi juga.

Menggunakan daftar monad, kodesemu imperatif

for a in (1, ..., 10)
   for b in (1, ..., 10)
      p <- a * b
      if even(p)
         yield p

diterjemahkan secara kasar ke blok do ,

do a <- [1 .. 10]
   b <- [1 .. 10]
   let p = a * b
   guard (even p)
   return p

pemahaman setara monad ,

[ p | a <- [1 .. 10], b <- [1 .. 10], let p = a * b, even p ]

dan ekspresinya

[1 .. 10] >>= (\ a ->
   [1 .. 10] >>= (\ b ->
      let p = a * b in
         guard (even p) >>       -- [ () | even p ] >>
            return p
      )
   )

Do notasi dan pemahaman monad adalah gula sintaksis untuk ekspresi ikatan terikat. Operator mengikat digunakan untuk pengikatan nama lokal dari hasil monadik.

let x = v in e    =   (\ x -> e)  $  v   =   v  &  (\ x -> e)
do { r <- m; c }  =   (\ r -> c) =<< m   =   m >>= (\ r -> c)

dimana

(&) :: a -> (a -> b) -> b
(&) = flip ($)

infixl 0 &

Fungsi penjaga didefinisikan

guard :: Additive m => Bool -> m ()
guard True  = return ()
guard False = fail

di mana jenis unit atau "tuple kosong"

data () = ()

Monad tambahan yang mendukung pilihan dan kegagalan dapat diabstraksi menggunakan kelas tipe

class Monad m => Additive m where
   fail  :: m t
   (<|>) :: m t -> m t -> m t

infixl 3 <|>

instance Additive Maybe where
   fail = Nothing

   Nothing <|> m = m
   m       <|> _ = m

instance Additive [] where
   fail = []
   (<|>) = (++)

di mana faildan <|>membentuk monoidforall k l m.

     k <|> fail  =  k
     fail <|> l  =  l
(k <|> l) <|> m  =  k <|> (l <|> m)

dan failmerupakan elemen nol yang menyerap / memusnahkan monad aditif

_ =<< fail  =  fail

Jika dalam

guard (even p) >> return p

even pbenar, maka penjaga menghasilkan [()], dan, dengan definisi >>, fungsi konstan lokal

\ _ -> return p

diterapkan pada hasilnya (). Jika salah, maka penjaga menghasilkan daftar monad's fail( []), yang tidak membuahkan hasil untuk panah Kleisli untuk diterapkan >>, jadi ini pdilewati.

Negara

Secara tidak terkenal, monad digunakan untuk menyandikan perhitungan stateful.

Sebuah prosesor negara adalah fungsi

forall st t. st -> (t, st)

yang mentransisikan keadaan stdan menghasilkan hasilnya t. The negara st bisa apa saja. Tidak ada, flag, count, array, handle, machine, world.

Jenis prosesor negara biasanya disebut

type State st t = st -> (t, st)

State processor monad adalah * -> *functor yang diketik State st. Panah Kleisli dari prosesor negara monad adalah fungsi

forall st a b. a -> (State st) b

Dalam Haskell kanonik, versi malas dari monad prosesor negara didefinisikan

newtype State st t = State { stateProc :: st -> (t, st) }

instance Functor (State st) where
   map :: (a -> b) -> ((State st) a -> (State st) b)
   map f (State p) = State $ \ s0 -> let (x, s1) = p s0
                                     in  (f x, s1)

instance Monad (State st) where
   return :: t -> (State st) t
   return x = State $ \ s -> (x, s)

   (=<<) :: (a -> (State st) b) -> (State st) a -> (State st) b
   f =<< (State p) = State $ \ s0 -> let (x, s1) = p s0
                                     in  stateProc (f x) s1

Prosesor status dijalankan dengan memasok kondisi awal:

run :: State st t -> st -> (t, st)
run = stateProc

eval :: State st t -> st -> t
eval = fst . run

exec :: State st t -> st -> st
exec = snd . run

Akses negara disediakan oleh primitif getdan put, metode abstraksi atas stateful monads:

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}

class Monad m => Stateful m st m -> st where
   get :: m st
   put :: st -> m ()

m -> stmendeklarasikan ketergantungan fungsional dari tipe negara stpada monad m; bahwa State t, misalnya, akan menentukan tipe negara untuk menjadi tunik.

instance Stateful (State st) st where
   get :: State st st
   get = State $ \ s -> (s, s)

   put :: st -> State st ()
   put s = State $ \ _ -> ((), s)

dengan tipe unit yang digunakan secara analog voiddalam C.

modify :: Stateful m st => (st -> st) -> m ()
modify f = do
   s <- get
   put (f s)

gets :: Stateful m st => (st -> t) -> m t
gets f = do
   s <- get
   return (f s)

gets sering digunakan dengan accessors bidang rekaman.

Negara setara monad dari variabel threading

let s0 = 34
    s1 = (+ 1) s0
    n = (* 12) s1
    s2 = (+ 7) s1
in  (show n, s2)

di mana s0 :: Int, adalah sama-sama transparan referensial, tetapi jauh lebih elegan dan praktis

(flip run) 34
   (do
      modify (+ 1)
      n <- gets (* 12)
      modify (+ 7)
      return (show n)
   )

modify (+ 1)adalah perhitungan tipe State Int (), kecuali efeknya setara dengan return ().

(flip run) 34
   (modify (+ 1) >>
      gets (* 12) >>= (\ n ->
         modify (+ 7) >>
            return (show n)
      )
   )

Hukum asosiatif monad dapat ditulis dalam istilah >>= forall m f g.

(m >>= f) >>= g  =  m >>= (\ x -> f x >>= g)

atau

do {                 do {                   do {
   r1 <- do {           x <- m;                r0 <- m;
      r0 <- m;   =      do {            =      r1 <- f r0;
      f r0                 r1 <- f x;          g r1
   };                      g r1             }
   g r1                 }
}                    }

Seperti dalam pemrograman berorientasi ekspresi (misalnya Rust), pernyataan terakhir dari sebuah blok mewakili hasilnya. Operator mengikat kadang-kadang disebut "titik koma diprogram".

Struktur kontrol iterasi primitif dari pemrograman imperatif terstruktur ditiru secara monadik

for :: Monad m => (a -> m b) -> [a] -> m ()
for f = foldr ((>>) . f) (return ())

while :: Monad m => m Bool -> m t -> m ()
while c m = do
   b <- c
   if b then m >> while c m
        else return ()

forever :: Monad m => m t
forever m = m >> forever m

Input output

data World

Prosesor negara dunia I / O adalah rekonsiliasi dari Haskell murni dan dunia nyata, dari semantik fungsional denotatif dan imperatif operasional. Sebuah analog dekat dari implementasi ketat yang sebenarnya:

type IO t = World -> (t, World)

Interaksi difasilitasi oleh primitif yang tidak murni

getChar         :: IO Char
putChar         :: Char -> IO ()
readFile        :: FilePath -> IO String
writeFile       :: FilePath -> String -> IO ()
hSetBuffering   :: Handle -> BufferMode -> IO ()
hTell           :: Handle -> IO Integer
. . .              . . .

Pengotor kode yang menggunakan IOprimitif secara permanen di-protokol-kan oleh sistem tipe. Karena kemurnian luar biasa, apa yang terjadi IO, tetap masuk IO.

unsafePerformIO :: IO t -> t

Atau, setidaknya, seharusnya.

Jenis tanda tangan dari program Haskell

main :: IO ()
main = putStrLn "Hello, World!"

meluas ke

World -> ((), World)

Fungsi yang mengubah dunia.

Epilog

Kategori objek mana yang merupakan tipe Haskell dan morfisme mana yang merupakan fungsi antara tipe Haskell adalah, “cepat dan longgar”, kategorinya Hask.

Functor Tadalah pemetaan dari kategori Cke kategori D; untuk setiap objek dalam Csuatu objek diD

Tobj :  Obj(C) -> Obj(D)
   f :: *      -> *

dan untuk setiap morfisme dalam Cmorfisme diD

Tmor :  HomC(X, Y) -> HomD(Tobj(X), Tobj(Y))
 map :: (a -> b)   -> (f a -> f b)

di mana X, Yadalah objek di C. HomC(X, Y)adalah kelas homomorfisme dari semua morfisme X -> Ydi C. Functor harus menjaga identitas dan komposisi morfisme, "struktur" dari C, di D.

                    Tmor    Tobj

      T(id)  =  id        : T(X) -> T(X)   Identity
T(f) . T(g)  =  T(f . g)  : T(X) -> T(Z)   Composition

The kategori Kleisli dari kategori Cdiberikan oleh Kleisli tiga

<T, eta, _*>

seorang endofunctor

T : C -> C

( f), morfisme identitas eta( return), dan operator ekstensi *( =<<).

Setiap morfisme Kleisli di Hask

      f :  X -> T(Y)
      f :: a -> m b

oleh operator ekstensi

   (_)* :  Hom(X, T(Y)) -> Hom(T(X), T(Y))
  (=<<) :: (a -> m b)   -> (m a -> m b)

diberikan morfisme dalam Haskkategori Kleisli

     f* :  T(X) -> T(Y)
(f =<<) :: m a  -> m b

Komposisi dalam kategori Kleisli .Tdiberikan dalam hal ekstensi

 f .T g  =  f* . g       :  X -> T(Z)
f <=< g  =  (f =<<) . g  :: a -> m c

dan memenuhi aksioma kategori

       eta .T g  =  g                :  Y -> T(Z)   Left identity
   return <=< g  =  g                :: b -> m c

       f .T eta  =  f                :  Z -> T(U)   Right identity
   f <=< return  =  f                :: c -> m d

  (f .T g) .T h  =  f .T (g .T h)    :  X -> T(U)   Associativity
(f <=< g) <=< h  =  f <=< (g <=< h)  :: a -> m d

yang, menerapkan transformasi kesetaraan

     eta .T g  =  g
     eta* . g  =  g               By definition of .T
     eta* . g  =  id . g          forall f.  id . f  =  f
         eta*  =  id              forall f g h.  f . h  =  g . h  ==>  f  =  g

(f .T g) .T h  =  f .T (g .T h)
(f* . g)* . h  =  f* . (g* . h)   By definition of .T
(f* . g)* . h  =  f* . g* . h     . is associative
    (f* . g)*  =  f* . g*         forall f g h.  f . h  =  g . h  ==>  f  =  g

dalam hal ekstensi diberikan secara kanonik

               eta*  =  id                 :  T(X) -> T(X)   Left identity
       (return =<<)  =  id                 :: m t -> m t

           f* . eta  =  f                  :  Z -> T(U)      Right identity
   (f =<<) . return  =  f                  :: c -> m d

          (f* . g)*  =  f* . g*            :  T(X) -> T(Z)   Associativity
(((f =<<) . g) =<<)  =  (f =<<) . (g =<<)  :: m a -> m c

Monads juga dapat didefinisikan bukan dari ekstensi Kleislian, tetapi transformasi alami mu, dalam pemrograman yang disebut join. Monad didefinisikan musebagai triple atas kategori C, dari endofunctor

     T :  C -> C
     f :: * -> *

dan dua tranformasi alami

   eta :  Id -> T
return :: t  -> f t

    mu :  T . T   -> T
  join :: f (f t) -> f t

memenuhi persamaan

       mu . T(mu)  =  mu . mu               :  T . T . T -> T . T   Associativity
  join . map join  =  join . join           :: f (f (f t)) -> f t

      mu . T(eta)  =  mu . eta       =  id  :  T -> T               Identity
join . map return  =  join . return  =  id  :: f t -> f t

Kelas tipe monad kemudian didefinisikan

class Functor m => Monad m where
   return :: t -> m t
   join   :: m (m t) -> m t

muImplementasi kanon opsi monad:

instance Monad Maybe where
   return = Just

   join (Just m) = m
   join Nothing  = Nothing

The concatfungsi

concat :: [[a]] -> [a]
concat (x : xs) = x ++ concat xs
concat []       = []

adalah joindaftar monad.

instance Monad [] where
   return :: t -> [t]
   return = (: [])

   (=<<) :: (a -> [b]) -> ([a] -> [b])
   (f =<<) = concat . map f

Implementasi joindapat diterjemahkan dari formulir ekstensi menggunakan kesetaraan

     mu  =  id*           :  T . T -> T
   join  =  (id =<<)      :: m (m t) -> m t

Terjemahan terbalik dari muke bentuk ekstensi diberikan oleh

     f*  =  mu . T(f)     :  T(X) -> T(Y)
(f =<<)  =  join . map f  :: m a -> m b

Tetapi mengapa teori yang begitu abstrak bisa digunakan untuk pemrograman?

Jawabannya sederhana: sebagai ilmuwan komputer, kami menghargai abstraksi ! Ketika kami mendesain antarmuka ke komponen perangkat lunak, kami ingin itu mengungkapkan sesedikit mungkin tentang implementasi. Kami ingin dapat mengganti implementasi dengan banyak alternatif, banyak 'contoh' lain dari 'konsep' yang sama. Ketika kita mendesain antarmuka generik ke banyak pustaka program, bahkan lebih penting lagi bahwa antarmuka yang kita pilih memiliki beragam implementasi. Ini adalah sifat umum dari konsep monad yang sangat kami hargai, itu karena teori kategori sangat abstrak sehingga konsepnya sangat berguna untuk pemrograman.

Maka, hampir tidak mengejutkan bahwa generalisasi monad yang kami sajikan di bawah ini juga memiliki hubungan dekat dengan teori kategori. Tetapi kami menekankan bahwa tujuan kami sangat praktis: ini bukan untuk 'menerapkan teori kategori', melainkan untuk menemukan cara yang lebih umum untuk menyusun perpustakaan kombinasi. Ini hanyalah keberuntungan kami bahwa matematikawan telah melakukan banyak pekerjaan untuk kami!

dari Generalising Monads ke Arrows oleh John Hughes


4

Apa yang dibutuhkan dunia adalah posting blog monad lain, tapi saya pikir ini berguna dalam mengidentifikasi monad yang ada di alam liar.

Segitiga Sierpinski

Di atas adalah fraktal yang disebut segitiga Sierpinski, satu-satunya fraktal yang bisa saya ingat untuk menggambar. Fraktal adalah struktur yang mirip dengan diri sendiri seperti segitiga di atas, di mana bagian-bagiannya mirip dengan keseluruhan (dalam hal ini tepat setengah skala sebagai segitiga induk).

Monad adalah fraktal. Diberikan struktur data monadik, nilainya dapat disusun untuk membentuk nilai lain dari struktur data. Inilah sebabnya mengapa ini berguna untuk pemrograman, dan inilah mengapa ini terjadi dalam banyak situasi.


3
Apakah maksud Anda "apa yang tidak dibutuhkan dunia ..."? Analogi yang bagus!
groverboy

@ icc97 Anda benar - artinya cukup jelas. Sarkasme yang tidak disengaja, permintaan maaf kepada penulis.
groverboy

Apa yang dunia butuhkan adalah utas komentar lain yang mengkonfirmasi sarkasme, tetapi jika membaca dengan seksama saya telah menulis tetapi itu harus membuatnya jelas.
Eugene Yokota


4

Biarkan " {| a |m}" di bawah ini mewakili sebagian data monadik. Tipe data yang mengiklankan a:

        (I got an a!)
          /        
    {| a |m}

Function,, ftahu cara membuat monad, jika saja itu memiliki a:

       (Hi f! What should I be?)
                      /
(You?. Oh, you'll be /
 that data there.)  /
 /                 /  (I got a b.)
|    --------------      |
|  /                     |
f a                      |
  |--later->       {| b |m}

Di sini kita melihat fungsinya,, fmencoba untuk mengevaluasi sebuah monad tetapi mendapat teguran.

(Hmm, how do I get that a?)
 o       (Get lost buddy.
o         Wrong type.)
o       /
f {| a |m}

Funtion,, fmenemukan cara untuk mengekstrak adengan menggunakan >>=.

        (Muaahaha. How you 
         like me now!?)       
    (Better.)      \
        |     (Give me that a.)
(Fine, well ok.)    |
         \          |
   {| a |m}   >>=   f

Sedikit yang ftahu, monad dan >>=berkolusi.

            (Yah got an a for me?)       
(Yeah, but hey    | 
 listen. I got    |
 something to     |
 tell you first   |
 ...)   \        /
         |      /
   {| a |m}   >>=   f

Tapi apa yang sebenarnya mereka bicarakan? Yah, itu tergantung monad. Berbicara semata-mata dalam abstrak memiliki penggunaan yang terbatas; Anda harus memiliki pengalaman dengan monad tertentu untuk menyempurnakan pemahaman.

Misalnya, tipe data Maybe

 data Maybe a = Nothing | Just a

memiliki turunan monad yang akan bertindak seperti berikut ...

Di mana, jika kasusnya Just a

            (Yah what is it?)       
(... hm? Oh,      |
forget about it.  |
Hey a, yr up.)    | 
            \     |
(Evaluation  \    |
time already? \   |
Hows my hair?) |  |
      |       /   |
      |  (It's    |
      |  fine.)  /
      |   /     /    
   {| a |m}   >>=   f

Tetapi untuk kasus Nothing

        (Yah what is it?)       
(... There      |
is no a. )      |
  |        (No a?)
(No a.)         |
  |        (Ok, I'll deal
  |         with this.)
   \            |
    \      (Hey f, get lost.) 
     \          |   ( Where's my a? 
      \         |     I evaluate a)
       \    (Not any more  |
        \    you don't.    |
         |   We're returning
         |   Nothing.)   /
         |      |       /
         |      |      /
         |      |     /
   {| a |m}   >>=   f      (I got a b.)
                    |  (This is   \
                    |   such a     \
                    |   sham.) o o  \
                    |               o|
                    |--later-> {| b |m}

Jadi, Mungkin monad memungkinkan perhitungan dilanjutkan jika benar-benar berisi aiklan yang diiklankannya, tetapi batalkan perhitungan jika tidak. Hasilnya, bagaimanapun masih sepotong data monadik, meskipun bukan output f. Untuk alasan ini, monad Mungkin digunakan untuk mewakili konteks kegagalan.

Monad yang berbeda berperilaku berbeda. Daftar adalah tipe data lain dengan instance monadik. Mereka berperilaku seperti berikut:

(Ok, here's your a. Well, its
 a bunch of them, actually.)
  |
  |    (Thanks, no problem. Ok
  |     f, here you go, an a.)
  |       |
  |       |        (Thank's. See
  |       |         you later.)
  |  (Whoa. Hold up f,      |
  |   I got another         |
  |   a for you.)           |
  |       |      (What? No, sorry.
  |       |       Can't do it. I 
  |       |       have my hands full
  |       |       with all these "b" 
  |       |       I just made.) 
  |  (I'll hold those,      |
  |   you take this, and   /
  |   come back for more  /
  |   when you're done   / 
  |   and we'll do it   / 
  |   again.)          /
   \      |  ( Uhhh. All right.)
    \     |       /    
     \    \      /
{| a |m}   >>=  f  

Dalam hal ini, fungsi tahu bagaimana membuat daftar dari inputnya, tetapi tidak tahu apa yang harus dilakukan dengan input ekstra dan daftar tambahan. Bind >>=, membantu fdengan menggabungkan beberapa output. Saya menyertakan contoh ini untuk menunjukkan bahwa walaupun >>=bertanggung jawab untuk mengekstraksi a, ia juga memiliki akses ke keluaran terikat akhirnya f. Memang, ia tidak akan pernah mengekstrak apa pun akecuali ia tahu keluaran akhirnya memiliki jenis konteks yang sama.

Ada monad lain yang digunakan untuk mewakili konteks yang berbeda. Berikut beberapa penokohan dari beberapa karakter lainnya. The IOmonad tidak benar-benar memiliki a, tapi ia tahu seorang pria dan akan mendapatkan bahwa auntuk Anda. The State stmonad memiliki simpanan rahasia styang akan lolos ke fbawah meja, meskipun fhanya datang meminta untuk a. The Reader rmonad ini mirip dengan State st, meskipun hanya memungkinkan fmelihat r.

Maksud dari semua ini adalah bahwa semua jenis data yang dinyatakan sebagai Monad menyatakan semacam konteks sekitar penggalian nilai dari monad. Keuntungan besar dari semua ini? Yah, cukup mudah untuk menulis perhitungan dengan semacam konteks. Namun, hal itu bisa berantakan ketika merangkai kalkulasi sarat berbagai konteks. Operasi monad menangani penyelesaian interaksi konteks sehingga programmer tidak perlu melakukannya.

Perhatikan, bahwa penggunaan >>=eases berantakan dengan mengambil beberapa otonomi dari f. Artinya, dalam kasus di Nothingatas misalnya, ftidak lagi bisa memutuskan apa yang harus dilakukan dalam kasus Nothing; itu dikodekan dalam >>=. Ini adalah trade off. Jika perlu untuk fmemutuskan apa yang harus dilakukan dalam kasus Nothing, maka fseharusnya fungsi dari Maybe ake Maybe b. Dalam hal ini, Maybemenjadi seorang monad tidak relevan.

Namun, perlu diketahui bahwa kadang-kadang tipe data tidak mengekspor konstruktornya (melihat Anda IO), dan jika kami ingin bekerja dengan nilai yang diiklankan, kami tidak punya banyak pilihan selain bekerja dengan antarmuka monadik itu.


3

Monad adalah benda yang digunakan untuk merangkum objek yang memiliki keadaan berubah. Paling sering dijumpai dalam bahasa-bahasa yang sebaliknya tidak memungkinkan Anda memiliki keadaan yang dapat dimodifikasi (mis., Haskell).

Contohnya adalah untuk file I / O.

Anda akan dapat menggunakan monad untuk file I / O untuk mengisolasi sifat keadaan yang berubah menjadi hanya kode yang menggunakan Monad. Kode di dalam Monad dapat secara efektif mengabaikan perubahan keadaan dunia di luar Monad - ini membuatnya lebih mudah untuk mempertimbangkan efek keseluruhan dari program Anda.


3
Seperti yang saya mengerti, monad lebih dari itu. Mengenkapsulasi keadaan yang bisa berubah dalam bahasa fungsional "murni" hanyalah satu aplikasi dari monad.
thSoft
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.