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 asebagai 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 Stringuntuk mewakili mencari Stringnilai 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 Functors atau Monads atau apa pun. Itu mengungkapkan cara umum menggunakan Maybe anilai ... 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 Monadatau apa pun.
Sebenarnya, inilah lembar contekan untuk semua yang benar - benar perlu Anda ketahui IOuntuk saat ini:
Jika sesuatu memiliki tipe IO a, itu berarti prosedur yang melakukan sesuatu dan mengeluarkan anilai.
Ketika Anda memiliki blok kode menggunakan donotasi, 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 voidfungsi 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" IOdari nilai, karena sesuatu di dalam IObukan nilai, itu adalah prosedur untuk mendapatkan nilai.
Baris terakhir dalam sebuah doblok 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, returnfungsi 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 doblok yang disebut di suatu tempat di mainmana 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 namea String, ini berarti helloprosedur 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?