Pertama mari kita bedakan antara mempelajari konsep-konsep abstrak dan mempelajari contoh-contoh spesifiknya .
Anda tidak akan terlalu jauh mengabaikan semua contoh spesifik, karena alasan sederhana bahwa mereka benar-benar ada di mana-mana. Faktanya, abstraksi ada sebagian besar karena mereka menyatukan hal-hal yang akan Anda lakukan dengan contoh-contoh spesifik.
Abstraksi itu sendiri, di sisi lain, tentu berguna , tetapi mereka tidak segera diperlukan. Anda bisa mengabaikan abstraksi sepenuhnya dan hanya menggunakan berbagai jenis secara langsung. Anda akan ingin memahami mereka pada akhirnya, tetapi Anda selalu dapat kembali lagi nanti. Bahkan, saya hampir dapat menjamin bahwa jika Anda melakukan itu, ketika Anda kembali ke sana Anda akan menampar dahi Anda dan bertanya-tanya mengapa Anda menghabiskan semua waktu itu melakukan hal-hal dengan cara yang sulit daripada menggunakan alat tujuan umum yang nyaman.
Ambil Maybe a
sebagai contoh. Itu hanya tipe data:
data Maybe a = Just a | Nothing
Semuanya mendokumentasikan diri; ini merupakan nilai opsional. Entah Anda memiliki "hanya" sesuatu jenis a
, atau Anda tidak punya apa-apa. Katakanlah Anda memiliki fungsi pencarian, yang kembali Maybe String
untuk mewakili mencari String
nilai yang mungkin tidak ada. Jadi Anda mencocokkan pola pada nilai untuk melihat yang mana itu:
case lookupFunc key of
Just val -> ...
Nothing -> ...
Itu saja!
Sungguh, tidak ada lagi yang Anda butuhkan. Tidak ada Functor
s atau Monad
s atau apa pun. Itu mengungkapkan cara umum menggunakan Maybe a
nilai ... tapi itu hanya idiom, "pola desain", apa pun yang Anda ingin menyebutnya.
Satu-satunya tempat Anda benar-benar tidak bisa menghindarinya sepenuhnya adalah dengan IO
, tetapi itu adalah kotak hitam yang misterius, jadi tidak ada gunanya mencoba memahami apa artinya sebagai Monad
atau apa pun.
Sebenarnya, inilah lembar contekan untuk semua yang benar - benar perlu Anda ketahui IO
untuk saat ini:
Jika sesuatu memiliki tipe IO a
, itu berarti prosedur yang melakukan sesuatu dan mengeluarkan a
nilai.
Ketika Anda memiliki blok kode menggunakan do
notasi, tulis sesuatu seperti ini:
do -- ...
inp <- getLine
-- etc...
... berarti menjalankan prosedur di sebelah kanan <-
, dan menetapkan hasilnya ke nama di sebelah kiri.
Sedangkan jika Anda memiliki sesuatu seperti ini:
do -- ...
let x = [foo, bar]
-- etc...
... itu berarti menetapkan nilai ekspresi polos (bukan prosedur) di sebelah kanan =
untuk ke nama di sebelah kiri.
Jika Anda meletakkan sesuatu di sana tanpa menetapkan nilai, seperti ini:
do putStrLn "blah blah, fishcakes"
... artinya menjalankan prosedur dan mengabaikan apa pun yang dikembalikan. Beberapa prosedur memiliki tipe IO ()
- ()
tipe ini adalah semacam placeholder yang tidak mengatakan apa-apa, sehingga hanya berarti prosedur melakukan sesuatu dan tidak mengembalikan nilai. Agak seperti void
fungsi dalam bahasa lain.
Menjalankan prosedur yang sama lebih dari satu kali dapat memberikan hasil yang berbeda; itu semacam ide. Inilah sebabnya mengapa tidak ada cara untuk "menghapus" IO
dari nilai, karena sesuatu di dalam IO
bukan nilai, itu adalah prosedur untuk mendapatkan nilai.
Baris terakhir dalam sebuah do
blok harus berupa prosedur sederhana tanpa penugasan, di mana nilai balik dari prosedur itu menjadi nilai balik untuk seluruh blok. Jika Anda ingin nilai kembali menggunakan beberapa nilai yang telah ditetapkan, return
fungsi tersebut mengambil nilai polos dan memberi Anda prosedur larangan yang mengembalikan nilai itu.
Selain itu, tidak ada yang istimewa tentang IO
; prosedur-prosedur ini sebenarnya adalah nilai-nilai sederhana itu sendiri, dan Anda dapat menyebarkannya dan menggabungkannya dengan cara yang berbeda. Hanya ketika mereka dieksekusi di do
blok yang disebut di suatu tempat di main
mana mereka melakukan apa pun.
Jadi, dalam sesuatu seperti program contoh stereotip yang benar-benar membosankan ini:
hello = do putStrLn "What's your name?"
name <- getLine
let msg = "Hi, " ++ name ++ "!"
putStrLn msg
return name
... Anda dapat membacanya seperti program penting. Kami mendefinisikan prosedur bernama hello
. Ketika dieksekusi, terlebih dahulu ia menjalankan prosedur untuk mencetak pesan yang menanyakan nama Anda; selanjutnya ia menjalankan prosedur yang membaca sebaris input, dan memberikan hasilnya kepada name
; lalu memberikan ekspresi pada nama msg
; lalu ia mencetak pesan; lalu mengembalikan nama pengguna sebagai hasil dari seluruh blok. Karena name
a String
, ini berarti hello
prosedur yang mengembalikan a String
, sehingga memiliki tipe IO String
. Dan sekarang Anda dapat menjalankan prosedur ini di tempat lain, sama seperti itu dijalankan getLine
.
Pfff, monad. Siapa yang butuh mereka?