Notasi do

Sequencing aksi IO dan bentuk ter-desugarnya, bind.

Daftar isi

Notasi do merangkai beberapa aksi IO menjadi satu urutan yang dijalankan dari atas ke bawah. Topik ini membahas penulisan blok do beserta aturan indentasinya, pengikatan hasil aksi dengan panah <-, deklarasi nilai murni dengan let, serta bentuk ter-desugarnya yang menggunakan operator (>>) dan (>>=). Setelah membacanya, urutan aksi dapat ditulis dengan benar dan padanan operatornya dapat dibaca ketika compiler atau dokumentasi menampilkannya.

Merangkai aksi dalam satu blok#

Blok do menggabungkan beberapa aksi menjadi satu aksi besar yang bertipe IO. Aksi di dalamnya dijalankan berurutan, satu setelah yang lain, sehingga urutan penulisan menentukan urutan efeknya. Notasi ini hanyalah gula sintaksis (syntactic sugar), yaitu penulisan yang lebih nyaman atas bentuk dasar yang lebih eksplisit, dan bentuk dasar tersebut dibahas pada bagian akhir topik ini.

Program berikut menuliskan sebuah berkas kecil di tengah dua pencetakan. Tiga aksi yang berbeda sifatnya, yaitu mencetak teks, menulis berkas, dan mencetak teks lagi, dirangkai dalam satu blok do yang tipenya IO ().

haskell catat-langkah.hs
main :: IO ()
main = do
  putStrLn "Pencatatan dimulai"
  writeFile "langkah.txt" "kompilasi selesai\n"
  putStrLn "Berkas langkah.txt ditulis"
text Keluaran
Pencatatan dimulai
Berkas langkah.txt ditulis

Aturan indentasi (layout rule) menentukan batas blok tanpa tanda kurung kurawal. Seluruh baris aksi harus dimulai pada kolom yang sama, yaitu sejajar dengan aksi pertama setelah kata do, sedangkan baris yang lebih masuk dianggap kelanjutan dari aksi sebelumnya. Editor Haskell memahami aturan ini, sehingga indentasi otomatisnya dapat diandalkan. Perubahan indentasi yang keliru memunculkan pesan parse error dari compiler sebelum program sempat berjalan.

Mengikat hasil aksi dengan panah#

Tanda panah <- menjalankan sebuah aksi lalu mengikat hasilnya kepada sebuah nama, sehingga nilai yang berasal dari dunia luar dapat digunakan oleh aksi berikutnya. Bentuknya nama <- aksi, dan tipe nama tersebut mengikuti hasil aksinya, misalnya isi <- readFile "pesan.txt" mengikat sebuah String. Pengikatan murni tanpa efek ditulis dengan let, yang bentuknya mirip tetapi tanpa panah karena tidak menjalankan apa pun.

haskell baca-pesan.hs
main :: IO ()
main = do
  writeFile "pesan.txt" "Halo dari berkas"
  isi <- readFile "pesan.txt"
  putStrLn ("Isi berkas: " ++ isi)
  let panjang = length isi
  putStrLn ("Panjang isi: " ++ show panjang)
text Keluaran
Isi berkas: Halo dari berkas
Panjang isi: 16

Program tersebut memuat dua jenis pengikatan sekaligus. Baris isi <- readFile "pesan.txt" menjalankan pembacaan berkas, kemudian hasilnya diikat kepada isi, sedangkan baris let panjang = length isi hanya memberi nama pada perhitungan murni tanpa efek apa pun. Keduanya boleh disusun berdampingan karena blok do menerima campuran aksi dan definisi murni secara bebas. Perbedaannya mudah diingat, yaitu panah selalu diikuti aksi yang berinteraksi dengan dunia luar, sedangkan let diikuti ekspresi murni.

Bentuk tanpa notasi do#

Compiler menerjemahkan setiap blok do ke bentuk dasarnya sebelum program dijalankan, proses yang disebut desugaring. Bentuk dasar itu menggunakan dua operator, yaitu (>>) untuk merangkai dua aksi berurutan dan (>>=) yang disebut bind untuk meneruskan hasil satu aksi kepada aksi berikutnya. Program berikut menampilkan dua pencetakan tanpa blok do sama sekali.

haskell tanpa-do.hs
-- dua aksi dirangkai dengan (>>)
main :: IO ()
main = putStrLn "baris pertama" >> putStrLn "baris kedua"
text Keluaran
baris pertama
baris kedua

Operator (>>) menjalankan aksi kiri, menunggu sampai selesai, lalu berlanjut ke aksi kanan, dan hasil aksi kiri dibuang karena memang tidak ada yang dibawa. Pengikatan hasil membutuhkan (>>=), yang menerima aksi pada sisi kiri dan fungsi pada sisi kanan. Program berikut identik dengan program pengikatan di atas, tetapi seluruhnya ditulis dengan bind dan lambda.

haskell baca-dengan-bind.hs
-- bentuk tanpa notasi do: bind meneruskan hasil kepada lambda
main :: IO ()
main =
  writeFile "pesan.txt" "Halo lagi" >>
  readFile "pesan.txt" >>= \isi ->
  putStrLn ("Hasil bacaan: " ++ isi)
text Keluaran
Hasil bacaan: Halo lagi

Pola umum pengikatannya persis seperti pada fragmen berikut. Baris isi <- aksiBaca yang diikuti aksi lanjutan isi setara dengan aksiBaca >>= \isi -> lanjutan isi, sedangkan perangkaian dua aksi tanpa hasil sama dengan (>>). Penulisan ulang ini dikerjakan compiler, sehingga tidak ada perbedaan perilaku antara notasi do dan bentuk bind. Bentuk bind itulah yang muncul pada tipe di dokumentasi pustaka standar, sehingga kebiasaan membacanya berguna ketika notasi do tidak lagi cukup nyaman.

haskell Contoh: padanan pengikatan hasil
-- tidak-dijalankan
do isi <- aksiBaca
   lanjutan isi

-- bentuk yang setara
aksiBaca >>= \isi -> lanjutan isi

Kesalahan umum#

Kesalahan yang paling sering terjadi pada blok do adalah indentasi yang tidak konsisten. Haskell menentukan batas blok dari kolom tempat aksi pertama dimulai, sehingga baris yang menjorok terlalu dalam atau kurang dalam dibaca sebagai kelanjutan yang tidak sah. Compiler umumnya menolak program tersebut dengan pesan parse error yang menyebut kemungkinan indentasi keliru. Perbaikannya berupa menyetarakan ulang seluruh baris aksi pada kolom yang sama dan membiarkan editor menyelaraskannya.

Kesalahan kedua berupa menggunakan <- di luar blok do, misalnya pada argumen fungsi yang menuntut ekspresi murni. Panah tersebut hanya berlaku di dalam blok do, sedangkan di tempat lain hasil aksi harus disambungkan melalui bind. Kesalahan ketiga berupa menganggap urutan penulisan baris yang berdekatan bebas ditukar, padahal efek dijalankan tepat sesuai urutan penulisannya. Apabila perilaku program terasa tertukar, periksa urutan baris aksi lebih dahulu sebelum menyalahkan logikanya.

Ringkasan#

  • Blok do merangkai beberapa aksi IO menjadi satu aksi yang dieksekusi dari atas ke bawah sesuai urutan penulisannya.
  • Panah <- mengikat hasil aksi kepada nama, sedangkan let memberi nama pada nilai murni tanpa efek.
  • Blok do diterjemahkan compiler menjadi (>>) untuk urutan aksi dan (>>=) untuk pengikatan hasil, tanpa perbedaan perilaku.
  • Indentasi menentukan batas blok do, sehingga seluruh baris aksi harus dimulai pada kolom yang sama.

Referensi#