Perbedaan antara State, ST, IORef, dan MVar

91

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 ?

John F. Miller
sumber

Jawaban:

119

The State Monad: model negara yang bisa berubah

State monad adalah lingkungan yang berfungsi murni untuk program dengan status, dengan API sederhana:

  • Dapatkan
  • taruh

Dokumentasi dalam paket mtl .

State monad biasanya digunakan saat membutuhkan state dalam satu thread kontrol. Itu tidak benar-benar menggunakan keadaan yang bisa berubah dalam implementasinya. Alih-alih, program ini diparameterisasi oleh nilai status (yaitu, status adalah parameter tambahan untuk semua komputasi). Status hanya tampak bermutasi dalam satu utas (dan tidak dapat dibagikan antar utas).

Monad ST dan STRefs

Monad ST adalah sepupu terbatas dari monad IO.

Ini memungkinkan keadaan berubah sewenang-wenang , diimplementasikan sebagai memori aktual yang dapat berubah pada mesin. API dibuat aman dalam program bebas efek samping, karena parameter tipe peringkat-2 mencegah nilai yang bergantung pada status yang bisa berubah keluar dari cakupan lokal.

Dengan demikian memungkinkan adanya mutabilitas terkontrol dalam program yang sebaliknya murni.

Biasa digunakan untuk larik yang bisa berubah dan struktur data lain yang bermutasi, lalu dibekukan. Ini juga sangat efisien, karena status yang bisa berubah adalah "akselerasi perangkat keras".

API Utama:

  • Control.Monad.ST
  • runST - memulai penghitungan efek memori baru.
  • Dan STRefs : menunjuk ke sel (lokal) yang bisa berubah.
  • Array berbasis ST (seperti vektor) juga umum.

Anggap saja sebagai saudara yang kurang berbahaya dari monad IO. Atau IO, di mana Anda hanya dapat membaca dan menulis ke memori.

IORef: STRef di IO

Ini adalah STRef (lihat di atas) di monad IO. Mereka tidak memiliki jaminan keamanan yang sama seperti STRef tentang lokalitas.

MVars: IORef dengan kunci

Seperti STRefs atau IORefs, tetapi dengan kunci terpasang, untuk akses bersamaan yang aman dari beberapa utas. IORef dan STRef hanya aman dalam pengaturan multi-utas saat menggunakan atomicModifyIORef(operasi atom bandingkan-dan-tukar). MVars adalah mekanisme yang lebih umum untuk berbagi status yang bisa berubah dengan aman.

Umumnya, di Haskell, gunakan MVars atau TVars (sel yang dapat berubah berbasis STM), melalui STRef atau IORef.

Don Stewart
sumber
3
Apa M di MVars dan T di TVars? Saya menebak "Dapat Berubah", "Transaksional". Menarik bagaimana ST berarti Thread Negara.
CMCDragonkai
10
Mengapa Anda mengatakan itu MVarharus lebih disukai STRef? STRefmenjamin 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?
Benjamin Hodgson
@CMCDragonkai Saya selalu menganggap M adalah singkatan dari mutex, tetapi saya tidak dapat menemukannya didokumentasikan di mana pun.
Andrew Thaddeus Martin
37

Oke, saya akan mulai dengan IORef. IORefmemberikan 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 beroperasi IO. 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" ).

MVars pada dasarnya sama dengan IORef, kecuali dua perbedaan yang sangat penting. MVaradalah primitif konkurensi, sehingga dirancang untuk akses dari banyak utas. Perbedaan kedua adalah an MVaradalah kotak yang bisa diisi atau kosong. Jadi di mana IORef Intselalu memiliki Int(atau terbawah), MVar Intmungkin memiliki Intatau mungkin kosong. Jika utas mencoba membaca nilai dari kosong MVar, itu akan memblokir sampai MVarterisi (oleh utas lain). Pada dasarnya MVar aadalah setara IORef (Maybe a)dengan semantik dengan ekstra yang berguna untuk konkurensi.

Stateadalah 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 tidak IO, Statemonad 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.

STadalah sesuatu yang sedikit berbeda. Struktur data utama dalam STadalah the STRef, yang mirip IOReftetapi dengan monad yang berbeda. The STpenggunaan 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 berarti STdapat menyediakan antarmuka murni saat menggunakan operasi tingkat rendah pada data yang dapat berubah, artinya sangat cepat. Dari perspektif program, seolah-olah STkomputasi berjalan di utas terpisah dengan penyimpanan lokal utas.

John L.
sumber
17

Orang lain telah melakukan hal-hal inti, tetapi menjawab pertanyaan langsung:

Semua ini membuat jenis garis ENV = IORef [(String, IORef LispVal)] membingungkan. Mengapa IORef kedua? Apa yang akan rusak jika saya melakukannya type ENV = State [(String, LispVal)]?

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 mengevaluasi printIt, Anda ingin lookup nama dari x dalam lingkungan awal di mana printItdidefinisikan, tetapi Anda ingin lookup nilai yang nama terikat dalam lingkungan di mana printItyang disebut (setelah setItmungkin 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.

sclv
sumber