Apakah IO monad secara teknis salah?

12

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 adiambil RealWorld -> (a, RealWorld)untuk membuat segalanya lebih dimengerti.

Cuplikan ini secara kondisional melakukan aksi di monad IO. Sekarang, dengan asumsi bahwa conditionadalah False, tindakan actiontidak 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 worldpada 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?

Lasse
sumber
Selamat Datang di Ilmu Komputer ! Pertanyaan Anda di luar topik di sini: kami menangani pertanyaan ilmu komputer , bukan pertanyaan pemrograman (lihat FAQ kami ). Pertanyaan Anda mungkin pada topik di Stack Overflow .
dkaeae
2
Menurut pendapat saya ini adalah pertanyaan ilmu komputer, karena berkaitan dengan semantik teoritis Haskell, bukan dengan pertanyaan pemrograman praktis.
Lasse
4
Saya tidak terlalu akrab dengan teori bahasa pemrograman, tapi saya pikir pertanyaan ini ada di sini. Mungkin membantu jika Anda menjelaskan apa yang 'salah' artinya di sini. Menurut Anda properti apa yang dimiliki mono IO yang seharusnya tidak dimiliki?
Kadal diskrit
1
Program ini tidak diketik dengan baik. Saya tidak yakin apa yang sebenarnya ingin Anda tulis. Definisi whendapat diketik, tetapi tidak memiliki jenis yang Anda nyatakan, dan saya tidak melihat apa yang membuat kode khusus ini menarik.
Gilles 'SANGAT berhenti menjadi jahat'
2
Program ini diambil kata demi kata dari halaman Haskell-wiki yang terhubung langsung di atas. Itu memang tidak mengetik. Ini karena ditulis dengan asumsi yang IO adidefinisikan sebagai RealWorld -> (a, RealWorld), untuk membuat internal IO lebih mudah dibaca.
Lasse

Jawaban:

12

Ini adalah "interpretasi" yang disarankan dari IOmonad. Jika Anda ingin menganggap serius "interpretasi" ini, maka Anda harus menganggap serius "RealWorld". Tidak relevan apakah action worldakan dievaluasi secara spekulatif atau tidak, actiontidak 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 adalah world. Kami tidak menggunakan alam semesta baru yang mungkin telah kami evaluasi secara spekulatif di samping. Keadaan alam semesta adalah world.

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. " RealWorldIni hanya 'token'. Ini sebenarnya bukan keadaan seluruh alam semesta." Oke, "interpretasi" ini tidak menjelaskan apa-apa. Namun demikian, sebagai detail implementasi , ini adalah model GHC IO. 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 memastikan RealWorlddan fungsi-fungsi khusus tidak dioptimalkan dengan cara yang mengubah perilaku dimaksudkan program.

Secara pribadi (seperti yang mungkin terbukti sekarang), saya pikir model "melintas dunia" IOini 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 IOsebagai 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:

data IO a = Return a | Request OSRequest (OSResponse -> IO a)

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 IOadalah salah satu program sepele Returnyang tidak melakukan apa-apa selain mengembalikan nilai, atau itu permintaan ke sistem operasi dengan penangan untuk tanggapan. OSRequestdapat berupa sesuatu seperti:

data OSRequest = OpenFile FilePath | PutStr String | ...

Demikian pula, OSResponsemungkin sesuatu seperti:

data OSResponse = Errno Int | OpenSucceeded Handle | ...

(Salah satu perbaikan yang dapat dilakukan adalah membuat hal-hal yang lebih ketik aman sehingga Anda tahu Anda tidak akan mendapatkan OpenSucceededdari PutStrpermintaan.) Model ini IOmenggambarkan permintaan yang ditafsirkan oleh beberapa sistem (untuk IOmonad "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 seperti PutStr "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 bahwa IO String, katakanlah, adalah "wadah" yang "berisi" String(dan kemudian <-mengeluarkannya). Pandangan permintaan tanggapan ini membuat perspektif ini jelas salah. Tidak ada pegangan file di dalamnya OpenFile "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 OSRequestlike Fork :: (OSResponse -> IO ()) -> OSRequestdan 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 kerja IOSpecperpustakaan .

1 Pelukan menggunakan implementasi berbasis kelanjutan IOyang 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-kira IO a = () -> ameskipun ()disebut World, tetapi tidak melakukan state-passing. JHC dan UHC pada dasarnya menggunakan pendekatan yang sama dengan GHC.

Derek Elkins meninggalkan SE
sumber
Terima kasih atas jawaban Anda yang mencerahkan, itu sangat membantu. Implementasi IO Anda membutuhkan waktu untuk membungkus pikiran saya, tetapi saya setuju bahwa itu lebih intuitif. Apakah Anda mengklaim bahwa implementasi ini tidak mengalami masalah potensial dengan pemesanan efek samping seperti yang dilakukan oleh RealWorld? Saya tidak bisa langsung melihat masalah, tetapi juga tidak jelas bagi saya bahwa mereka tidak ada.
Lasse
Satu komentar: sepertinya itu OpenFile "foo" (\r -> ...)seharusnya Request (OpenFile "foo") (\r -> ...)?
Lasse
@Lasse Yap, seharusnya dengan Request. Untuk menjawab pertanyaan pertama Anda, ini IOjelas 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. Dalam whencontoh, tidak masalah jika actiondievaluasi, karena itu hanya akan menjadi nilai seperti Request (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.
Derek Elkins meninggalkan SE
Ah ya saya lihat itu. Ini definisi yang sangat pintar. Pada awalnya saya berpikir bahwa semua efek samping harus terjadi ketika seluruh program selesai dieksekusi, karena Anda harus membangun struktur data sebelum dapat menafsirkannya. Tetapi karena permintaan berisi kelanjutan, Anda hanya perlu membuat data yang paling pertama Requestuntuk mulai melihat efek samping. Efek samping selanjutnya dapat dibuat ketika mengevaluasi kelanjutan. Pintar!
Lasse