Maaf untuk pertanyaan efek samping FP + lainnya, tetapi saya tidak dapat menemukan yang sudah ada yang menjawab ini untuk saya.
Pemahaman saya (terbatas) tentang pemrograman fungsional adalah bahwa keadaan / efek samping harus diminimalkan dan dipisahkan dari logika stateless.
Saya juga mengumpulkan pendekatan Haskell untuk ini, monad IO, mencapai ini dengan membungkus tindakan negara dalam sebuah wadah, untuk kemudian dieksekusi, dianggap di luar lingkup program itu sendiri.
Saya mencoba memahami pola ini, tetapi sebenarnya untuk menentukan apakah akan menggunakannya dalam proyek Python, jadi ingin menghindari spesifik Haskell jika poss.
Contoh kasar masuk.
Jika program saya mengonversi file XML ke file JSON:
def main():
xml_data = read_file('input.xml') # impure
json_data = convert(xml_data) # pure
write_file('output.json', json_data) # impure
Bukankah pendekatan IO monad efektif untuk melakukan ini:
steps = list(
read_file,
convert,
write_file,
)
kemudian membebaskan diri dari tanggung jawab dengan tidak benar-benar memanggil langkah-langkah itu, tetapi membiarkan penerjemah melakukannya?
Atau dengan kata lain, itu seperti menulis:
def main(): # pure
def inner(): # impure
xml_data = read_file('input.xml')
json_data = convert(xml_data)
write_file('output.json', json_data)
return inner
kemudian mengharapkan orang lain untuk menelepon inner()
dan mengatakan pekerjaan Anda selesai karena main()
itu murni.
Seluruh program pada akhirnya akan terkandung dalam monad IO, pada dasarnya.
Ketika kode benar-benar dieksekusi , semuanya setelah membaca file tergantung pada status file itu sehingga masih akan menderita bug terkait negara yang sama dengan implementasi imperatif, jadi apakah Anda benar-benar mendapatkan sesuatu, sebagai programmer yang akan mempertahankan ini?
Saya benar-benar menghargai manfaat mengurangi dan mengisolasi perilaku negara, yang sebenarnya mengapa saya menyusun versi imperatif seperti itu: mengumpulkan input, melakukan hal-hal murni, mengeluarkan output. Semoga convert()
bisa benar-benar murni dan menuai manfaat cachability, threadsafety, dll.
Saya juga menghargai bahwa jenis monadik dapat bermanfaat, terutama dalam jaringan pipa yang beroperasi pada jenis yang sebanding, tetapi tidak melihat mengapa IO harus menggunakan monad kecuali sudah ada dalam pipa seperti itu.
Apakah ada manfaat tambahan untuk berurusan dengan efek samping yang dibawa oleh pola IO monad, yang saya lewatkan?
main
dalam program Haskell adalahIO ()
- tindakan IO. Ini sebenarnya bukan fungsi sama sekali; itu sebuah nilai . Seluruh program Anda adalah nilai murni yang berisi instruksi yang memberi tahu runtime bahasa apa yang harus dilakukan. Semua hal yang tidak murni (benar-benar melakukan tindakan IO) berada di luar cakupan program Anda.read_file
) dan menggunakannya sebagai argumen ke yang berikutnya (write_file
). Jika Anda hanya memiliki urutan tindakan independen, Anda tidak perlu Monad.Jawaban:
Itulah bagian di mana saya pikir Anda tidak melihatnya dari sudut pandang Haskellers. Jadi kami memiliki program seperti ini:
Saya pikir pendapat khas Haskeller tentang hal ini adalah
convert
, bagian yang murni:IO
bagian - bagiannya;IO
sama sekali.Sehingga mereka tidak melihat ini sebagai
convert
menjadi "terkandung" dalamIO
, melainkan, karena yang terisolasi dariIO
. Dari tipenya, apa pun yangconvert
dilakukannya tidak pernah bisa bergantung pada apa pun yang terjadi dalam suatuIO
tindakan.Saya akan mengatakan bahwa ini terbagi menjadi dua hal:
convert
tergantung pada keadaan file.convert
fungsi tidak , yang tidak bergantung pada keadaan file.convert
selalu memiliki fungsi yang sama , bahkan jika dipanggil dengan argumen yang berbeda pada titik yang berbeda.Ini adalah poin yang agak abstrak, tetapi itu benar-benar kunci untuk apa yang dimaksud Haskellers ketika mereka membicarakan hal ini. Anda ingin menulis
convert
sedemikian rupa yang diberikan setiap argumen yang valid, maka akan menghasilkan hasil yang benar untuk argumen itu. Ketika Anda melihatnya seperti itu, fakta bahwa membaca file adalah operasi stateful tidak masuk ke dalam persamaan; yang penting adalah bahwa argumen apa pun yang diberikan padanya dan dari mana pun itu berasal,convert
harus menanganinya dengan benar. Dan fakta bahwa kemurnian membatasi apa yangconvert
dapat dilakukan dengan inputnya menyederhanakan alasan itu.Jadi jika
convert
menghasilkan hasil yang salah dari beberapa argumen, danreadFile
mengumpankannya dengan argumen seperti itu, kami tidak melihatnya sebagai bug yang diperkenalkan oleh negara . Ini bug dalam fungsi murni!sumber
Sulit untuk memastikan apa yang Anda maksud dengan "murni akademis", tetapi saya pikir jawabannya sebagian besar "tidak".
Seperti dijelaskan dalam Tackling the Awkward Squad oleh Simon Peyton Jones ( sangat disarankan membaca!), I / O monadik dimaksudkan untuk memecahkan masalah nyata dengan cara yang digunakan Haskell untuk menangani I / O. Baca contoh server dengan Permintaan dan Tanggapan, yang tidak akan saya salin di sini; ini sangat instruktif.
Haskell, tidak seperti Python, mendorong gaya komputasi "murni" yang dapat ditegakkan oleh sistem tipenya. Tentu saja, Anda bisa menggunakan disiplin diri ketika memprogram dengan Python untuk mematuhi gaya ini, tetapi bagaimana dengan modul yang tidak Anda tulis? Tanpa banyak bantuan dari sistem tipe (dan perpustakaan umum), monadic I / O mungkin kurang berguna dalam Python. Filsafat bahasa tidak hanya dimaksudkan untuk menegakkan pemisahan murni / tidak murni yang ketat.
Perhatikan bahwa ini mengatakan lebih banyak tentang filosofi berbeda Haskell dan Python daripada tentang bagaimana akademik monadik I / O. Saya tidak akan menggunakannya untuk Python.
Satu hal lagi. Kamu bilang:
Memang benar
main
fungsi Haskell "hidup"IO
, tetapi program Haskell yang sebenarnya dianjurkan untuk tidak menggunakanIO
kapan pun itu tidak diperlukan. Hampir setiap fungsi yang Anda tulis yang tidak perlu dilakukan I / O tidak boleh memiliki tipeIO
.Jadi saya akan mengatakan dalam contoh terakhir Anda mendapatkannya mundur:
main
tidak murni (karena membaca dan menulis file) tetapi fungsi inti seperticonvert
murni.sumber
Mengapa IO tidak murni? Karena dapat mengembalikan nilai yang berbeda pada waktu yang berbeda. Ada ketergantungan pada waktu yang harus dipertanggungjawabkan, dengan satu atau lain cara. Ini bahkan lebih penting dengan evaluasi malas. Pertimbangkan program berikut:
Tanpa mono IO, mengapa prompt pertama akan mendapatkan output? Tidak ada yang bergantung padanya, jadi evaluasi malas berarti tidak akan pernah dituntut. Juga tidak ada yang mendorong prompt untuk menjadi output sebelum input dibaca. Sejauh menyangkut komputer, tanpa monad IO, dua ekspresi pertama tersebut sepenuhnya independen satu sama lain. Untungnya,
name
memberlakukan pesanan pada dua yang kedua.Ada cara-cara lain untuk menyelesaikan masalah ketergantungan pada pesanan, tetapi menggunakan monad IO mungkin merupakan cara paling sederhana (dari sudut pandang bahasa setidaknya) untuk memungkinkan semuanya tetap di ranah fungsional yang malas, tanpa sedikit bagian kode imperatif. Ini juga yang paling fleksibel. Misalnya, Anda dapat dengan relatif mudah membangun pipa IO secara dinamis saat runtime berdasarkan input pengguna.
sumber
Itu bukan hanya pemrograman fungsional; itu biasanya ide bagus dalam bahasa apa pun. Jika Anda melakukan pengujian unit, cara Anda berpisah
read_file()
,convert()
danwrite_file()
datang secara alami karena, meskipunconvert()
sejauh ini merupakan bagian yang paling kompleks dan terbesar dari kode, menulis tes untuk itu relatif mudah: yang Anda perlukan hanyalah parameter input . Menulis tes untukread_file()
danwrite_file()
sedikit lebih sulit (walaupun fungsinya sendiri hampir sepele) karena Anda perlu membuat dan / atau membaca hal-hal pada sistem file sebelum dan sesudah memanggil fungsi. Idealnya Anda akan membuat fungsi-fungsi seperti itu begitu sederhana sehingga Anda merasa nyaman tidak mengujinya dan dengan demikian menghemat banyak kesulitan.Perbedaan antara Python dan Haskell di sini adalah bahwa Haskell memiliki pemeriksa tipe yang dapat membuktikan bahwa fungsi tidak memiliki efek samping. Dalam Python Anda perlu berharap bahwa tidak ada orang yang secara tidak sengaja memasukkan fungsi membaca file atau menulis ke
convert()
(katakanlah,read_config_file()
). Di Haskell ketika Anda mendeklarasikanconvert :: String -> String
atau serupa, tanpaIO
monad, pemeriksa tipe akan menjamin bahwa ini adalah fungsi murni yang hanya bergantung pada parameter inputnya dan tidak ada yang lain. Jika seseorang mencoba memodifikasiconvert
untuk membaca file konfigurasi, mereka akan dengan cepat melihat kesalahan kompiler yang menunjukkan bahwa mereka akan merusak kemurnian fungsi. (Dan mudah-mudahan mereka cukup masuk akal untukread_config_file
keluarconvert
dan memberikan hasilnyaconvert
, menjaga kemurnian.)sumber