Tampilan # 1 dan # 2 secara umum salah.
- Jenis data apa pun
* -> *dapat berfungsi sebagai label, monad jauh lebih dari itu.
- (Dengan pengecualian
IOmonad) perhitungan dalam monad tidak tidak murni. Mereka hanya mewakili perhitungan yang kami anggap memiliki efek samping, tetapi murni.
Kedua kesalahpahaman ini berasal dari fokus pada IOmonad, yang sebenarnya agak istimewa.
Saya akan mencoba menguraikan sedikit tentang # 3, tanpa masuk ke dalam teori kategori jika memungkinkan.
Perhitungan standar
Semua perhitungan dalam bahasa pemrograman fungsional dapat dilihat sebagai fungsi dengan jenis sumber dan jenis target: f :: a -> b. Jika suatu fungsi memiliki lebih dari satu argumen, kita dapat mengonversinya menjadi fungsi satu argumen dengan memilah (lihat juga Haskell wiki ). Dan jika kita hanya memiliki nilai x :: a(fungsi dengan 0 argumen), kita bisa mengubahnya menjadi fungsi yang mengambil argumen dari jenis unit : (\_ -> x) :: () -> a.
Kita dapat membangun program yang lebih kompleks dari yang lebih sederhana dengan menyusun fungsi-fungsi tersebut menggunakan .operator. Misalnya, jika kita miliki f :: a -> bdan g :: b -> ckita dapatkan g . f :: a -> c. Perhatikan bahwa ini juga berfungsi untuk nilai yang dikonversi: Jika kami memiliki x :: adan mengonversinya menjadi representasi kami, kami mendapatkannya f . ((\_ -> x) :: () -> a) :: () -> b.
Representasi ini memiliki beberapa sifat yang sangat penting, yaitu:
- Kami memiliki fungsi yang sangat istimewa - fungsi identitas
id :: a -> a untuk setiap jenis a. Ini adalah elemen identitas sehubungan dengan .: fsama dengan f . iddan untuk id . f.
- Operator komposisi fungsi
.bersifat asosiatif .
Perhitungan monadik
Misalkan kita ingin memilih dan bekerja dengan beberapa kategori perhitungan khusus, yang hasilnya berisi lebih dari sekedar nilai pengembalian tunggal. Kami tidak ingin menentukan apa artinya "sesuatu yang lebih", kami ingin menjaga hal-hal yang bersifat umum. Cara paling umum untuk merepresentasikan "sesuatu yang lebih" adalah merepresentasikannya sebagai fungsi tipe - suatu mjenis * -> *(yaitu mengkonversi satu tipe ke yang lain). Jadi untuk setiap kategori perhitungan yang ingin kami kerjakan, kami akan memiliki beberapa fungsi tipe m :: * -> *. (Dalam Haskell, madalah [], IO, Maybe, dll) dan kategori akan berisi semua fungsi dari jenis a -> m b.
Sekarang kami ingin bekerja dengan fungsi-fungsi dalam kategori seperti itu dengan cara yang sama seperti pada kasus dasar. Kami ingin dapat menyusun fungsi-fungsi ini, kami ingin komposisi menjadi asosiatif, dan kami ingin memiliki identitas. Kita butuh:
- Untuk memiliki operator (sebut saja
<=<) yang menyusun fungsi f :: a -> m bdan g :: b -> m cmenjadi sesuatu sebagai g <=< f :: a -> m c. Dan, itu harus asosiatif.
- Untuk memiliki beberapa fungsi identitas untuk setiap jenis, sebut saja
return. Kami juga ingin itu f <=< returnsama fdan sama dengan return <=< f.
Apa pun m :: * -> *yang kami punya fungsi seperti itu returndan <=<disebut monad . Hal ini memungkinkan kita untuk membuat perhitungan yang kompleks dari yang lebih sederhana, seperti pada kasus dasar, tetapi sekarang jenis nilai pengembalian diubah oleh m.
(Sebenarnya, saya sedikit menyalahgunakan istilah kategori di sini. Dalam pengertian teori-kategori, kita dapat menyebut konstruksi kita sebagai kategori hanya setelah kita tahu itu mematuhi hukum-hukum ini.)
Monads di Haskell
Dalam Haskell (dan bahasa fungsional lainnya) kami sebagian besar bekerja dengan nilai, bukan dengan fungsi tipe () -> a. Jadi alih-alih mendefinisikan <=<untuk setiap monad, kami mendefinisikan fungsi (>>=) :: m a -> (a -> m b) -> m b. Definisi alternatif semacam itu setara, kita bisa ungkapkan >>=menggunakan <=<dan sebaliknya (coba sebagai latihan, atau lihat sumbernya ). Prinsipnya kurang jelas sekarang, tetapi tetap sama: hasil kami selalu tipe m adan kami menyusun fungsi tipe a -> m b.
Untuk setiap monad yang kita buat, kita tidak boleh lupa untuk memeriksanya returndan <=<memiliki properti yang kami butuhkan: asosiatif dan identitas kiri / kanan. Dinyatakan menggunakan returndan >>=mereka disebut hukum monad .
Contoh - daftar
Jika kita memilih muntuk menjadi [], kita mendapatkan kategori fungsi tipe a -> [b]. Fungsi tersebut mewakili perhitungan non-deterministik, yang hasilnya bisa satu atau lebih nilai, tetapi juga tidak ada nilai. Ini memunculkan apa yang disebut daftar monad . Komposisi f :: a -> [b]dan g :: b -> [c]berfungsi sebagai berikut: g <=< f :: a -> [c]berarti menghitung semua hasil yang mungkin dari jenis [b], berlaku guntuk masing-masing, dan mengumpulkan semua hasil dalam satu daftar. Disajikan dalam Haskell
return :: a -> [a]
return x = [x]
(<=<) :: (b -> [c]) -> (a -> [b]) -> (a -> [c])
g (<=<) f = concat . map g . f
atau menggunakan >>=
(>>=) :: [a] -> (a -> [b]) -> [b]
x >>= f = concat (map f x)
Perhatikan bahwa dalam contoh ini, tipe-tipe kembalinya [a]sangat mungkin sehingga mereka tidak mengandung nilai tipe apa pun a. Memang, tidak ada persyaratan untuk monad sehingga tipe pengembalian harus memiliki nilai seperti itu. Beberapa monad selalu memiliki (suka IOatau State), tetapi beberapa tidak, suka []atau Maybe.
IO Monad
Seperti yang saya sebutkan, IOmonad agak istimewa. Nilai tipe IO aberarti nilai tipe yang adibangun dengan berinteraksi dengan lingkungan program. Jadi (tidak seperti semua monad lainnya), kita tidak dapat menggambarkan nilai tipe IO amenggunakan beberapa konstruksi murni. Berikut IOini hanyalah tag atau label yang membedakan komputasi yang berinteraksi dengan lingkungan. Ini (satu-satunya kasus) di mana tampilan # 1 dan # 2 benar.
Untuk IOmonad:
- Komposisi
f :: a -> IO bdan g :: b -> IO ccara: Menghitung fyang berinteraksi dengan lingkungan, dan kemudian menghitung gyang menggunakan nilai dan menghitung hasil yang berinteraksi dengan lingkungan.
returncukup tambahkan IO"tag" ke nilai (kami hanya "menghitung" hasilnya dengan menjaga lingkungan tetap utuh).
- Hukum monad (asosiatif, identitas) dijamin oleh kompiler.
Beberapa catatan:
- Karena perhitungan monadik selalu memiliki tipe hasil
m a, tidak ada cara bagaimana "melarikan diri" dari IOmonad. Artinya adalah: Setelah perhitungan berinteraksi dengan lingkungan, Anda tidak dapat membuat perhitungan dari itu yang tidak.
- Ketika seorang programmer fungsional tidak tahu bagaimana membuat sesuatu dengan cara murni, (s) ia dapat (sebagai upaya terakhir ) memprogram tugas dengan beberapa perhitungan stateful dalam
IOmonad. Inilah sebabnya mengapa IOsering disebut sin bin programmer .
- Perhatikan bahwa dalam dunia yang tidak murni (dalam arti pemrograman fungsional) membaca nilai dapat mengubah lingkungan juga (seperti mengonsumsi input pengguna). Itu sebabnya fungsi seperti
getCharharus memiliki tipe hasil IO something.