Kami mulai melihat manfaat efek samping sebagai nilai kelas satu, jadi mari beralih ke tingkat yang lebih tinggi. Kita telah melihat bahwa Haskell memungkinkan kita untuk menyimpan objek efek samping dalam variabel tanpa mengeksekusi efeknya secara tidak sengaja. Langkah selanjutnya adalah menyimpan objek efek samping ini dalam struktur data.
Misalnya, kita dapat membuat daftar tiga cara berbeda untuk mendapatkan nama pengguna pengguna yang sedang login.
getting_usernames :: (IO (Maybe String)) getting_usernames = (lookupEnv "USER", lookupEnv "LOGNAME", lookupEnv "SUDO_USER")
Daftar ini tidak dapat dijalankan karena efek sampingnya secara langsung, karena daftar itu sendiri bukanlah efek samping – melainkan daftar efek samping. Ada fungsi perpustakaan untuk mengatasi hal ini. Salah satunya adalah
sequenceA :: (IO a) -> IO (a)
Inilah yang kita perlukan dalam kasus ini: dibutuhkan daftar objek efek samping dan membuat objek efek samping baru yang mengeksekusi semua efek samping dari daftar tersebut, dan kemudian menghasilkan daftar semua nilai yang dihasilkan oleh efek samping tersebut. Untuk membuat efek samping yang menghasilkan daftar calon nama pengguna, kami mendefinisikannya
actual_usernames :: IO (Maybe String) actual_usernames = sequenceA getting_usernames
Jika kita menjalankan efek samping ini dan mencetak hasilnya (baik dengan menghubungkannya dengan print
menggunakan >>=
operator, atau dalam a do
blok), lalu di sistem saya kami mendapatkan hasilnya
(Just "kqr", Just "kqr", Nothing)
Kadang-kadang kita mempunyai daftar efek samping tetapi kita tidak peduli dengan nilai yang dihasilkannya. Hal ini mungkin terjadi jika kita telah mengumpulkan sekumpulan pernyataan log dari fungsi murni. Kami ingin mengeksekusi efek sampingnya (tindakan logging sebenarnya) tetapi kami tidak peduli dengan apa yang dikembalikan oleh fungsi log itu sendiri (biasanya berupa nilai void atau tipe unit.)
log_statements :: (IO ()) log_statements = ( log Info "Creating user", log Warn "User already found" log Info "Updating user" )
Sebagai pengingat: fungsi ini memanggil ke log
fungsi tidak menyebabkan apa pun dicatat. Itu log
fungsi mengembalikan objek efek samping yang, ketika dijalankan, membuat pencatatan terjadi. Itu log_statements
variabel berisi daftar objek efek samping tersebut – variabel itu sendiri bukanlah objek efek samping.
Untuk menjalankannya, kita dapat menggabungkan kembali efek samping dalam daftar menjadi satu objek efek samping sequenceA
. Saat kita melakukannya, kita mendapatkan objek efek samping yang menghasilkan nilai ((), (), ())
. Agar kode dapat diketik, kita mungkin harus membuang nilai ini. Kita sudah tahu bagaimana melakukan hal ini, karena membuang nilai adalah hal yang paling penting *>
operator melakukannya.
execute_logged :: IO () execute_logged = sequenceA log_statements *> pure ()
Ketika efek samping dari execute_logged
dijalankan, ini akan menjalankan efek samping dari pernyataan log dan kemudian membuang nilai dummy yang dihasilkan dalam proses tersebut.
Ingat bahwa dadu yang dimuat sebelumnya? Sekarang kita dapat memeriksa apakah memang selalu mengembalikan nomor yang sama. Pertama kita membuat daftar dengan mengulangi objek efek samping yang sama berkali-kali.
many_loaded_dice :: (IO Int) many_loaded_dice = repeat loaded_die
Kemudian kita membuat objek efek samping yang mengeksekusi beberapa efek pertama dan mempertahankan nilai yang dihasilkannya.
some_thrown_dice :: IO (Int) some_thrown_dice = sequenceA (take 20 many_loaded_dice)
Jika kita menghubungkan ini dengan cetakan (sekali lagi, do
blok atau itu >>=
operator) dan jalankan, kita dapatkan
(4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4)
Kita bisa melakukan hal yang sama dengan dadu yang lebih baik:
many_good_dice :: (IO Int) many_good_dice = repeat (randomRIO (1,6)) some_thrown_dice :: IO (Int) some_thrown_dice = sequenceA (take 20 many_good_dice)
Jika kita mencetak ini sekali, kita mendapatkannya
(2,1,4,1,3,2,2,2,1,4,6,4,1,4,6,4,5,3,4,6)
Jika kita mencetaknya lagi, kita mungkin mendapatkannya
(4,5,3,2,4,2,5,4,6,1,1,5,1,3,6,4,4,5,1,4)
Meskipun kami menyusun daftarnya dengan mengulangi sama objek efek samping, kita mendapatkan sekumpulan angka acak baru setiap kali objek efek samping gabungan dijalankan. Ini menjadi bukti tambahan bahwa yang kita simpan di daftar bukanlah akibat efek sampingnya, melainkan efek samping itu sendiri.
Tapi perhatikan juga apa yang kami lakukan. Kami menggunakan fungsi daftar (repeat
, take 20
) untuk memanipulasi struktur data objek efek samping seolah-olah itu adalah nilai reguler – karena memang demikian! Kemudian kami menggunakan fungsi manipulasi efek samping (sequenceA
) untuk menggabungkan efek samping dalam daftar menjadi satu objek efek samping baru yang mengeksekusi semuanya. Ini adalah sejenis pemrograman meta, hanya saja tidak menggunakan bahasa makro khusus tetapi dilakukan pada tingkat nilai reguler.