Di wiki haskell ada contoh berikut penggunaan bersyarat dari IO monad (lihat di sini) .
when :: Bool -> IO () -> IO ()
when condition action world =
if condition
then action world
else ((), world)
Perhatikan bahwa dalam contoh ini, definisi IO a
diambil RealWorld -> (a, RealWorld)
untuk membuat segalanya lebih dimengerti.
Cuplikan ini secara kondisional melakukan aksi di monad IO. Sekarang, dengan asumsi bahwa condition
adalah False
, tindakan action
tidak boleh dieksekusi. Menggunakan semantik malas ini memang akan terjadi. Namun, dicatat di sini bahwa Haskell secara teknis tidak-tegas. Ini berarti bahwa kompiler diizinkan untuk, misalnya, secara preemptif dijalankan action world
pada utas yang berbeda, dan kemudian membuang perhitungan itu ketika ia menemukan itu tidak memerlukannya. Namun, pada titik itu efek samping sudah akan terjadi.
Sekarang, orang mungkin menerapkan monad IO sedemikian rupa sehingga efek samping hanya disebarkan ketika seluruh program selesai, dan kita tahu persis efek samping mana yang harus dieksekusi. Namun, ini bukan masalahnya, karena dimungkinkan untuk menulis program tak terbatas di Haskell, yang jelas memiliki efek samping menengah.
Apakah ini berarti bahwa IO monad secara teknis salah, atau ada hal lain yang mencegah hal ini terjadi?
when
dapat diketik, tetapi tidak memiliki jenis yang Anda nyatakan, dan saya tidak melihat apa yang membuat kode khusus ini menarik.IO a
didefinisikan sebagaiRealWorld -> (a, RealWorld)
, untuk membuat internal IO lebih mudah dibaca.Jawaban:
Ini adalah "interpretasi" yang disarankan dari
IO
monad. Jika Anda ingin menganggap serius "interpretasi" ini, maka Anda harus menganggap serius "RealWorld". Tidak relevan apakahaction world
akan dievaluasi secara spekulatif atau tidak,action
tidak memiliki efek samping, efeknya, jika ada, ditangani dengan mengembalikan keadaan alam semesta baru di mana efek-efek tersebut terjadi, misalnya paket jaringan telah dikirim. Namun, hasil dari fungsinya adalah((),world)
dan oleh karena itu keadaan baru dari alam semesta adalahworld
. Kami tidak menggunakan alam semesta baru yang mungkin telah kami evaluasi secara spekulatif di samping. Keadaan alam semesta adalahworld
.Anda mungkin mengalami kesulitan untuk menganggapnya serius. Ada banyak cara yang paling paradoksal dan tidak masuk akal. Konkurensi sangat tidak jelas atau gila dengan perspektif ini.
"Tunggu, tunggu," katamu. "
RealWorld
Ini hanya 'token'. Ini sebenarnya bukan keadaan seluruh alam semesta." Oke, "interpretasi" ini tidak menjelaskan apa-apa. Namun demikian, sebagai detail implementasi , ini adalah model GHCIO
. 1 Namun, ini berarti bahwa kita memang memiliki "fungsi" ajaib yang sebenarnya memiliki efek samping dan model ini tidak memberikan panduan untuk artinya. Dan, karena fungsi-fungsi ini sebenarnya memiliki efek samping, kekhawatiran yang Anda ajukan benar-benar tepat sasaran. GHC tidak harus pergi keluar dari cara untuk memastikanRealWorld
dan fungsi-fungsi khusus tidak dioptimalkan dengan cara yang mengubah perilaku dimaksudkan program.Secara pribadi (seperti yang mungkin terbukti sekarang), saya pikir model "melintas dunia"
IO
ini tidak berguna dan membingungkan sebagai alat pedagogis. (Apakah ini berguna untuk implementasi, saya tidak tahu. Untuk GHC, saya pikir ini lebih merupakan artefak sejarah.)Salah satu pendekatan alternatif adalah dengan melihat
IO
sebagai menggambarkan permintaan dengan penangan respons. Ada beberapa cara untuk melakukan ini. Mungkin yang paling mudah diakses adalah menggunakan konstruksi monad gratis, khususnya yang bisa kita gunakan:Ada banyak cara untuk membuat ini lebih canggih dan memiliki sifat yang agak lebih baik, tetapi ini sudah merupakan perbaikan. Itu tidak membutuhkan asumsi filosofis yang mendalam tentang sifat realitas untuk memahami. Semua itu menyatakan bahwa itu
IO
adalah salah satu program sepeleReturn
yang tidak melakukan apa-apa selain mengembalikan nilai, atau itu permintaan ke sistem operasi dengan penangan untuk tanggapan.OSRequest
dapat berupa sesuatu seperti:Demikian pula,
OSResponse
mungkin sesuatu seperti:(Salah satu perbaikan yang dapat dilakukan adalah membuat hal-hal yang lebih ketik aman sehingga Anda tahu Anda tidak akan mendapatkan
OpenSucceeded
dariPutStr
permintaan.) Model iniIO
menggambarkan permintaan yang ditafsirkan oleh beberapa sistem (untukIO
monad "nyata" ini adalah runtuhnya Haskell itu sendiri), dan kemudian, mungkin, sistem itu akan memanggil penangan yang telah kami berikan tanggapan. Ini, tentu saja, juga tidak memberikan indikasi bagaimana permintaan sepertiPutStr "hello world"
harus ditangani, tetapi juga tidak berpura-pura. Itu membuat eksplisit bahwa ini sedang didelegasikan ke beberapa sistem lain. Model ini juga cukup akurat. Semua program pengguna di OS modern perlu membuat permintaan ke OS untuk melakukan apa saja.Model ini memberikan intuisi yang tepat. Sebagai contoh, banyak pemula melihat hal-hal seperti
<-
operator sebagai "membuka"IO
, atau memiliki (sayangnya diperkuat) pandangan bahwaIO String
, katakanlah, adalah "wadah" yang "berisi"String
(dan kemudian<-
mengeluarkannya). Pandangan permintaan tanggapan ini membuat perspektif ini jelas salah. Tidak ada pegangan file di dalamnyaOpenFile "foo" (\r -> ...)
. Sebuah analogi umum untuk menekankan ini adalah bahwa tidak ada kue di dalam resep untuk kue (atau mungkin "faktur" akan lebih baik dalam hal ini).Model ini juga mudah digunakan dengan konkurensi. Kita dapat dengan mudah memiliki konstruktor untuk
OSRequest
likeFork :: (OSResponse -> IO ()) -> OSRequest
dan kemudian runtime dapat menginterleave permintaan yang dihasilkan oleh penangan tambahan ini dengan penangan normal seperti yang diinginkannya. Dengan kepintaran Anda dapat menggunakan ini (atau teknik terkait) untuk benar-benar memodelkan hal-hal seperti konkurensi lebih langsung daripada hanya mengatakan "kami membuat permintaan ke OS dan hal-hal terjadi." Beginilah cara kerjaIOSpec
perpustakaan .1 Pelukan menggunakan implementasi berbasis kelanjutan
IO
yang kira-kira mirip dengan apa yang saya jelaskan meskipun dengan fungsi buram bukan tipe data eksplisit. HBC juga menggunakan implementasi berbasis kelanjutan yang dilapisi IO berbasis aliran respons-permintaan lama. NHC (dan dengan demikian YHC) menggunakan thunks, yaitu kira-kiraIO a = () -> a
meskipun()
disebutWorld
, tetapi tidak melakukan state-passing. JHC dan UHC pada dasarnya menggunakan pendekatan yang sama dengan GHC.sumber
OpenFile "foo" (\r -> ...)
seharusnyaRequest (OpenFile "foo") (\r -> ...)
?Request
. Untuk menjawab pertanyaan pertama Anda, iniIO
jelas tidak sensitif terhadap urutan evaluasi (modulo bottoms) karena ini adalah nilai yang lembam. Semua efek samping (jika ada) akan dibuat oleh hal yang menginterpretasikan nilai ini. Dalamwhen
contoh, tidak masalah jikaaction
dievaluasi, karena itu hanya akan menjadi nilai sepertiRequest (PutStr "foo") (...)
yang tidak akan kami berikan pada hal yang menafsirkan permintaan ini. Ini seperti kode sumber; tidak masalah jika Anda menguranginya dengan bersemangat atau malas, tidak ada yang terjadi sampai diberikan kepada penerjemah.Request
untuk mulai melihat efek samping. Efek samping selanjutnya dapat dibuat ketika mengevaluasi kelanjutan. Pintar!