Saya sedang mengerjakan Tulis Skema Sendiri dalam 48 Jam (saya memiliki waktu hingga sekitar 85 jam) dan saya telah sampai pada bagian tentang Menambahkan Variabel dan Tugas . Ada lompatan konseptual yang besar dalam bab ini, dan saya berharap hal itu dilakukan dalam dua langkah dengan refactoring yang baik di antaranya daripada langsung melompat ke solusi akhir. Bagaimanapun…
Aku sudah tersesat dengan sejumlah kelas yang berbeda yang tampaknya untuk melayani tujuan yang sama: State
, ST
, IORef
, dan MVar
. Tiga yang pertama disebutkan dalam teks, sedangkan yang terakhir tampaknya menjadi jawaban yang disukai untuk banyak pertanyaan StackOverflow tentang tiga yang pertama. Mereka semua tampaknya membawa keadaan antara doa yang berurutan.
Apa masing-masing ini dan bagaimana perbedaannya satu sama lain?
Secara khusus, kalimat berikut tidak masuk akal:
Sebagai gantinya, kami menggunakan fitur yang disebut utas status , membiarkan Haskell mengelola status agregat untuk kami. Ini memungkinkan kita memperlakukan variabel yang bisa berubah seperti yang kita lakukan dalam bahasa pemrograman lain, menggunakan fungsi untuk mendapatkan atau mengatur variabel.
dan
Modul IORef memungkinkan Anda menggunakan variabel stateful dalam monad IO .
Semua ini membuat garisnya type ENV = IORef [(String, IORef LispVal)]
membingungkan - mengapa yang kedua IORef
? Apa yang akan rusak jika saya type ENV = State [(String, LispVal)]
malah menulis ?
MVar
harus lebih disukaiSTRef
?STRef
menjamin bahwa hanya satu utas yang dapat memutasinya (dan bukan jenis IO lain yang dapat terjadi) - tentunya itu lebih baik jika saya tidak memerlukan akses bersamaan ke status yang dapat berubah?Oke, saya akan mulai dengan
IORef
.IORef
memberikan nilai yang dapat berubah di monad IO. Ini hanya referensi ke beberapa data, dan seperti referensi lainnya, ada fungsi yang memungkinkan Anda mengubah data yang dirujuknya. Di Haskell, semua fungsi tersebut beroperasiIO
. Anda dapat menganggapnya seperti database, file, atau penyimpanan data eksternal lainnya - Anda bisa mendapatkan dan mengatur data di dalamnya, tetapi untuk melakukannya perlu melalui IO. Alasan IO diperlukan adalah karena Haskell murni ; kompilator memerlukan cara untuk mengetahui data mana yang dirujuk referensi pada waktu tertentu (baca blogpost sigfpe "Anda bisa saja menemukan monads" ).MVar
s pada dasarnya sama dengan IORef, kecuali dua perbedaan yang sangat penting.MVar
adalah primitif konkurensi, sehingga dirancang untuk akses dari banyak utas. Perbedaan kedua adalah anMVar
adalah kotak yang bisa diisi atau kosong. Jadi di manaIORef Int
selalu memilikiInt
(atau terbawah),MVar Int
mungkin memilikiInt
atau mungkin kosong. Jika utas mencoba membaca nilai dari kosongMVar
, itu akan memblokir sampaiMVar
terisi (oleh utas lain). Pada dasarnyaMVar a
adalah setaraIORef (Maybe a)
dengan semantik dengan ekstra yang berguna untuk konkurensi.State
adalah monad yang menyediakan status yang bisa berubah, tidak harus dengan IO. Faktanya, ini sangat berguna untuk komputasi murni. Jika Anda memiliki algoritme yang menggunakan status tetapi tidakIO
,State
monad sering kali merupakan solusi yang elegan.Ada juga trafo monad versi State ,
StateT
. Ini sering digunakan untuk menyimpan data konfigurasi program, atau jenis status "game-world-state" dalam aplikasi.ST
adalah sesuatu yang sedikit berbeda. Struktur data utama dalamST
adalah theSTRef
, yang miripIORef
tetapi dengan monad yang berbeda. TheST
penggunaan monad jenis sistem tipuan ( "negara benang" docs menyebutkan) untuk memastikan bahwa data bisa berubah tidak bisa lepas monad tersebut; yaitu, ketika Anda menjalankan komputasi ST, Anda mendapatkan hasil yang murni. Alasan ST menarik adalah karena ia adalah monad primitif seperti IO, memungkinkan komputasi untuk melakukan manipulasi tingkat rendah pada bytearrays dan pointer. Ini berartiST
dapat menyediakan antarmuka murni saat menggunakan operasi tingkat rendah pada data yang dapat berubah, artinya sangat cepat. Dari perspektif program, seolah-olahST
komputasi berjalan di utas terpisah dengan penyimpanan lokal utas.sumber
Orang lain telah melakukan hal-hal inti, tetapi menjawab pertanyaan langsung:
Lisp adalah bahasa fungsional dengan status dan cakupan leksikal yang bisa berubah. Bayangkan Anda telah menutup variabel yang bisa berubah. Sekarang Anda memiliki referensi ke variabel ini yang berkeliaran di dalam beberapa fungsi lain - katakanlah (dalam pseudocode gaya haskell)
(printIt, setIt) = let x = 5 in (\ () -> print x, \y -> set x y)
. Anda sekarang memiliki dua fungsi - satu mencetak x, dan satu lagi menetapkan nilainya. Ketika Anda mengevaluasiprintIt
, Anda ingin lookup nama dari x dalam lingkungan awal di manaprintIt
didefinisikan, tetapi Anda ingin lookup nilai yang nama terikat dalam lingkungan di manaprintIt
yang disebut (setelahsetIt
mungkin telah dipanggil sejumlah kali ).Ada beberapa cara selain kedua IORef untuk melakukan ini, tetapi Anda pasti membutuhkan lebih dari tipe yang terakhir Anda usulkan, yang tidak memungkinkan Anda untuk mengubah nilai-nilai yang terikat pada nama dengan cara cakupan leksikal. Google "masalah funarg" untuk banyak prasejarah yang menarik.
sumber