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.
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.
Jawaban:
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 Error
monad, 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
,, digit
dll. 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 async
monad mampu "split" blok sendiri dan menunda pelaksanaan paruh kedua. ( async {}
Sintaks menunjukkan bahwa aliran kontrol di blok ditentukan oleh async
monad.)
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 bind
operator (dieja >>=
dalam Haskell). Karena bind
operasi 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
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.
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
bind
dapat melakukan sedikit lebih banyak daripada fmap
, tetapi tidak sebaliknya. Sebenarnya, fmap
dapat didefinisikan hanya dari segi bind
dan return
. Jadi, ketika mendefinisikan sebuah monad .. Anda memberikan tipenya (ini dia Wrapped a
) dan kemudian mengatakan bagaimana itu return
dan bind
operasi 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.
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.
Monad adalah tipe data yang memiliki dua operasi: >>=
(alias bind
) dan return
(alias unit
). return
mengambil 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 return
harus 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 Just
dan Nothing
adalah Maybe
konstruktor. 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.
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 T
yang terdapat fungsi tingkat tinggi, sebut saja map
, bahwa transformasi fungsi dari jenis a -> b
(diberi dua jenis a
dan b
) ke dalam fungsi T a -> T b
. map
Fungsi ini juga harus mematuhi hukum-hukum identitas dan komposisi sehingga ungkapan berikut ini berlaku untuk semua p
dan q
(notasi Haskell):
map id = id
map (p . q) = map p . map q
Misalnya, tipe konstruktor yang dipanggil List
adalah functor jika dilengkapi dengan fungsi tipe (a -> b) -> List a -> List b
yang mematuhi hukum di atas. Satu-satunya implementasi praktis sudah jelas. Fungsi yang dihasilkan List a -> List b
berulang di atas daftar yang diberikan, memanggil (a -> b)
fungsi untuk setiap elemen, dan mengembalikan daftar hasil.
Sebuah monad dasarnya hanya functor T
dengan dua metode ekstra, join
, jenis T (T a) -> T a
, dan unit
(kadang-kadang disebut return
, fork
atau pure
) tipe a -> T a
. Untuk daftar di Haskell:
join :: [[a]] -> [a]
pure :: a -> [a]
Mengapa itu bermanfaat? Karena Anda bisa, misalnya, map
pada daftar dengan fungsi yang mengembalikan daftar. Join
mengambil daftar daftar yang dihasilkan dan menggabungkannya. List
adalah 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 join
harus asosiatif. Ini berarti bahwa jika Anda memiliki nilai x
tipe [[[a]]]
maka join (join x)
harus sama join (map join x)
. Dan pure
harus menjadi identitas untuk join
itu join (pure x) == x
.
[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:
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 Maybe
atau a IO
.
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 Int
bisa a Just Int
atau Nothing
. Sekarang, jika Anda menambahkan a Maybe Int
ke Maybe Int
, operator akan memeriksa untuk melihat apakah keduanya ada Just Int
di dalam, dan jika demikian, akan membuka bungkusnya Int
, meneruskannya ke operator tambahan, membungkus ulang hasilnya Int
menjadi yang baru Just Int
(yang valid Maybe Int
), dan dengan demikian mengembalikan a Maybe Int
. Tetapi jika salah satunya ada Nothing
di dalam, operator ini akan langsung kembali Nothing
, yang lagi adalah valid Maybe Int
. Dengan begitu, Anda dapat berpura-pura bahwa Maybe Int
angka 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 Nothing
mana - mana .
Tetapi contohnya adalah apa yang terjadi Maybe
. Jika informasi tambahan adalah IO
, maka operator khusus yang ditentukan untuk IO
s akan dipanggil, dan itu bisa melakukan sesuatu yang sama sekali berbeda sebelum melakukan penambahan. (Oke, menambahkan dua IO Int
s bersama-sama mungkin tidak masuk akal - saya belum yakin.) (Juga, jika Anda memperhatikan Maybe
contoh, 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.
M (M a) -> M a
. Fakta bahwa Anda dapat mengubahnya menjadi salah satu jenis M a -> (a -> M b) -> M b
adalah apa yang membuatnya berguna.
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:
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 lift
operator 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.
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 y
nilai dapat menjadi Mb
seperti, misalnya, (is_OK, b)
jika y
menyertakan 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 bool
menunjukkan 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 bool
bagian dari sebuah tuple.
(Ma -> Mb) >> (Mb -> Mc)
Di sini, sekali lagi, komposisi terjadi secara alami compose
dan 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_OK
itu 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 composition
alih - alih compose
. Dengan demikian, alih-alih hanya mentransfer nilai dari output satu fungsi ke input yang lain Bind
akan memeriksa M
bagian Ma
dan memutuskan apakah dan bagaimana menerapkan fungsi yang dikomposisikan ke a
. Tentu saja, fungsi bind
akan didefinisikan secara khusus untuk kita M
sehingga dapat memeriksa strukturnya dan melakukan apa pun jenis aplikasi yang kita inginkan. Meskipun demikian, a
dapat berupa apa saja karena bind
hanya meneruskan yang tidak a
terinspeksi ke fungsi yang dikomposisikan ketika itu menentukan aplikasi yang diperlukan. Selain itu, fungsi yang dikomposisikan sendiri tidak perlu lagi berurusan denganM
sebagian 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 content
model di mana shell berisi data yang relevan dengan penerapan fungsi yang disusun dan diinterogasi oleh dan tetap hanya tersedia untuk bind
fungsi tersebut.
Karena itu, monad adalah tiga hal:
M
shell untuk menyimpan informasi yang relevan monad, bind
fungsi yang diimplementasikan untuk memanfaatkan informasi shell ini dalam penerapan fungsi yang dikomposisikan ke nilai konten yang ditemukannya di dalam shell, dan 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, Mb
struktur hasil umumnya sangat bermanfaat. Misalnya, operator divisi tidak mengembalikan nomor ketika pembagi itu 0
.
Selain itu, monad
s dapat mencakup fungsi bungkus yang membungkus nilai,, a
ke 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 bind
fungsi 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)
Associativity
berarti bind
mempertahankan urutan evaluasi terlepas dari kapan bind
diterapkan. Artinya, dalam definisi Associativity
di atas, gaya evaluasi awal kurung binding
dari f
dan g
hanya akan menghasilkan fungsi yang mengharapkan Ma
untuk menyelesaikan bind
. Oleh karena itu evaluasi Ma
harus ditentukan sebelum nilainya dapat diterapkan f
dan hasilnya pada gilirannya diterapkan g
.
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:
(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.
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 if
monad 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.
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.
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.
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.
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.
Monad Bukan Metafora , tetapi abstraksi praktis yang berguna muncul dari pola umum, seperti dijelaskan Daniel Spiewak.
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 data
dan data mengalir melalui pipa.
Dalam praktiknya, monad adalah implementasi kustom dari operator komposisi fungsi yang menangani efek samping dan nilai input dan return yang tidak kompatibel (untuk rantai).
Jika saya mengerti dengan benar, IEnumerable diturunkan dari monads. Saya bertanya-tanya apakah itu mungkin sudut pendekatan yang menarik bagi kita dari dunia C #?
Untuk apa nilainya, berikut adalah beberapa tautan ke tutorial yang membantu saya (dan tidak, saya masih belum mengerti apa itu monad).
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.
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.
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 map
dan flatten
. Meskipun mereka setara di bawah pemetaan tertentu. Presentasi ini sangat bagus: http://www.slideshare.net/samthemonad/monad-presentation-scala-as-a-category
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, "">
f
mengambil 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 f
dan g
bertanggung jawab untuk menambahkan pesan log mereka sendiri ke informasi logging sebelumnya. (Bayangkan saja demi argumen bahwa alih-alih menambahkan string, f
dang
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 m
ke f
". Untuk pakan pasangan <x, messages>
ke dalam fungsi f
adalah 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 x
dan 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 g
dan 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 x
apa. Jika x
tipe t
, maka pasangan Anda adalah nilai tipe "pair of t
and string". Panggil tipe itu M t
.
M
adalah konstruktor tipe: M
sendirian tidak merujuk ke tipe, tetapi M _
merujuk ke tipe setelah Anda mengisi kosong dengan tipe. An M int
adalah sepasang int dan string. An M string
adalah 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.feed
mengambil (fungsi yang mengambil a t
dan mengembalikan sebuah M u
) dan an M t
dan mengembalikan sebuahM u
.wrap
mengambil v
dan mengembalikan sebuah M v
.t
,, u
dan v
tiga jenis yang mungkin sama atau tidak sama. Monad memenuhi tiga properti yang Anda buktikan untuk monad spesifik Anda:
Memberi makan yang dibungkus t
dengan suatu fungsi sama dengan melewatkan yang tidak dibungkus t
ke dalam fungsi.
Secara formal: feed(f, wrap(x)) = f(x)
Memberi makan M t
ke dalam wrap
tidak ada hubungannya dengan M t
.
Secara formal: feed(wrap, m) = m
Mengumpankan M t
(sebut saja m
) ke fungsi itu
t
ke dalamg
M u
(sebut saja n
) darig
n
kef
sama dengan
m
keg
n
darig
n
kef
Secara formal: di feed(h, m) = feed(f, feed(g, m))
manah(x) := feed(f, g(x))
Biasanya, feed
disebut bind
(AKA >>=
di Haskell) dan wrap
disebut return
.
Saya akan mencoba menjelaskan Monad
dalam 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 -> String
danf :: String -> Bool
.
Kita dapat melakukan (f . g) x
, yang sama dengan f (g x)
, di mana x
adalahInt
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 g
harus 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 Int
tipe tersebut merepresentasikan Int
nilai yang mungkin tidak ada, IO String
tipe tersebut mewakili String
nilai yang ada sebagai hasil dari melakukan beberapa efek samping.)
Katakanlah sekarang kita punya g1 :: Int -> Maybe String
dan f1 :: String -> Maybe Bool
. g1
dan f1
sangat mirip dengan g
danf
masing masing.
Kita tidak bisa melakukan (f1 . g1) x
atau f1 (g1 x)
, di mana x
merupakan Int
nilai. Jenis hasil yang dikembalikan g1
bukan apaf1
diharapkan.
Kita bisa menulis f
dan g
dengan .
operator, tetapi sekarang kita tidak bisa menulis f1
dan g1
dengan .
. 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 g1
dan f1
, sehingga kita bisa menulis (f1 OPERATOR g1) x
? g1
mengembalikan 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 x
adalah sebuah Maybe Int
nilai. The >>=
Operator membantu mengambil Int
nilai dari "mungkin-tidak-ada" konteks, dan menerapkannya ke f1
. Hasil f1
, yang merupakan Maybe Bool
, akan menjadi hasil dari seluruh >>=
operasi.
Dan akhirnya, mengapa Monad
bermanfaat? Karena Monad
adalah tipe kelas yang mendefinisikan >>=
operator, sangat mirip dengan Eq
kelas tipe yang mendefinisikan ==
dan /=
operator.
Untuk menyimpulkan, Monad
kelas 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 Monad
memungkinkan komposisi fungsi yang melibatkan nilai-nilai dalam konteks .
{-# 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
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 id
merupakan identitas kanan dan kirinya.
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 f
tipe * -> *
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 r
terdiri 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 a
dan 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 t
dalam konteks monadik m
. Aplikasi ekstensi atau Kleisli =<<
menerapkan panah Kleisli a -> m b
ke 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 return
merupakan identitas kanan dan kirinya.
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
Jenis opsi
data Maybe t = Nothing | Just t
mengkodekan perhitungan Maybe t
yang 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 b
diterapkan pada hasil hanya jika Maybe a
menghasilkan 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 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 x
panah Kleisli a -> [b]
hingga elemen [a]
ke dalam daftar hasil tunggal [b]
.
Biarkan pembagi yang tepat dari bilangan bulat positif n
menjadi
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 fail
dan <|>
membentuk monoidforall k l m.
k <|> fail = k
fail <|> l = l
(k <|> l) <|> m = k <|> (l <|> m)
dan fail
merupakan elemen nol yang menyerap / memusnahkan monad aditif
_ =<< fail = fail
Jika dalam
guard (even p) >> return p
even p
benar, 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 p
dilewati.
Secara tidak terkenal, monad digunakan untuk menyandikan perhitungan stateful.
Sebuah prosesor negara adalah fungsi
forall st t. st -> (t, st)
yang mentransisikan keadaan st
dan 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 get
dan 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 -> st
mendeklarasikan ketergantungan fungsional dari tipe negara st
pada monad m
; bahwa State t
, misalnya, akan menentukan tipe negara untuk menjadi t
unik.
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 void
dalam 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
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 IO
primitif 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.
Kategori objek mana yang merupakan tipe Haskell dan morfisme mana yang merupakan fungsi antara tipe Haskell adalah, “cepat dan longgar”, kategorinya Hask
.
Functor T
adalah pemetaan dari kategori C
ke kategori D
; untuk setiap objek dalam C
suatu objek diD
Tobj : Obj(C) -> Obj(D)
f :: * -> *
dan untuk setiap morfisme dalam C
morfisme diD
Tmor : HomC(X, Y) -> HomD(Tobj(X), Tobj(Y))
map :: (a -> b) -> (f a -> f b)
di mana X
, Y
adalah objek di C
. HomC(X, Y)
adalah kelas homomorfisme dari semua morfisme X -> Y
di 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 C
diberikan 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 Hask
kategori Kleisli
f* : T(X) -> T(Y)
(f =<<) :: m a -> m b
Komposisi dalam kategori Kleisli .T
diberikan 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 mu
sebagai 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
mu
Implementasi kanon opsi monad:
instance Monad Maybe where
return = Just
join (Just m) = m
join Nothing = Nothing
The concat
fungsi
concat :: [[a]] -> [a]
concat (x : xs) = x ++ concat xs
concat [] = []
adalah join
daftar monad.
instance Monad [] where
return :: t -> [t]
return = (: [])
(=<<) :: (a -> [b]) -> ([a] -> [b])
(f =<<) = concat . map f
Implementasi join
dapat diterjemahkan dari formulir ekstensi menggunakan kesetaraan
mu = id* : T . T -> T
join = (id =<<) :: m (m t) -> m t
Terjemahan terbalik dari mu
ke bentuk ekstensi diberikan oleh
f* = mu . T(f) : T(X) -> T(Y)
(f =<<) = join . map f :: m a -> m b
Philip Wadler: Monads untuk pemrograman fungsional
Simon L Peyton Jones, Philip Wadler: pemrograman fungsional imperatif
Jonathan MD Hill, Keith Clarke: Pengantar teori kategori, teori kategori monad, dan hubungannya dengan pemrograman fungsional ´
Eugenio Moggi: Pengertian tentang komputasi dan monad
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
Apa yang dibutuhkan dunia adalah posting blog monad lain, tapi saya pikir ini berguna dalam mengidentifikasi monad yang ada di alam liar.
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.
http://code.google.com/p/monad-tutorial/ adalah pekerjaan yang sedang berlangsung untuk menjawab pertanyaan ini dengan tepat.
Biarkan " {| a |m}
" di bawah ini mewakili sebagian data monadik. Tipe data yang mengiklankan a
:
(I got an a!)
/
{| a |m}
Function,, f
tahu 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,, f
mencoba 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,, f
menemukan cara untuk mengekstrak a
dengan menggunakan >>=
.
(Muaahaha. How you
like me now!?)
(Better.) \
| (Give me that a.)
(Fine, well ok.) |
\ |
{| a |m} >>= f
Sedikit yang f
tahu, 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 a
iklan 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 f
dengan 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 a
kecuali 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 IO
monad tidak benar-benar memiliki a
, tapi ia tahu seorang pria dan akan mendapatkan bahwa a
untuk Anda. The State st
monad memiliki simpanan rahasia st
yang akan lolos ke f
bawah meja, meskipun f
hanya datang meminta untuk a
. The Reader r
monad ini mirip dengan State st
, meskipun hanya memungkinkan f
melihat 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 Nothing
atas misalnya, f
tidak lagi bisa memutuskan apa yang harus dilakukan dalam kasus Nothing
; itu dikodekan dalam >>=
. Ini adalah trade off. Jika perlu untuk f
memutuskan apa yang harus dilakukan dalam kasus Nothing
, maka f
seharusnya fungsi dari Maybe a
ke Maybe b
. Dalam hal ini, Maybe
menjadi 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.
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.