Block adalah potongan kode yang dilekatkan pada satu pemanggilan metode dan dijalankan oleh metode tersebut, bukan objek yang berdiri sendiri. Topik ini membahas cara menuliskan block pada pemanggilan metode, cara yield menjadikannya titik pemanggilan, penerusan argumen ke block, nilai balik block, pemanggilan yang berulang, serta penegakan aturan block oleh compiler. Setelah membaca topik ini, metode yang menerima block dapat dibaca dan ditulis dengan mengetahui kontrak yang mengikat kedua sisinya.
Menulis block pada pemanggilan metode#
Block ditulis langsung setelah argumen pemanggilan metode, baik dengan do ... end maupun dengan kurung kurawal { ... }. Block tidak memiliki nama dan tidak dapat dipanggil dari tempat lain karena masa hidupnya melekat pada satu pemanggilan metode itu saja. Di dalam tubuh metode, kata kunci yield menandai titik pemanggilan block. Metode berikut tidak memiliki parameter apa pun, tetapi tubuhnya memanggil block dua kali.
# yield memanggil block dua kali
def dua_kali
yield
yield
end
dua_kali do
puts "Piket pagi"
endJalankan berkasnya dengan perintah berikut.
# jalankan program dua_kali.cr
crystal run dua_kali.crPiket pagi
Piket pagiDefinisi dua_kali tidak memuat parameter, sedangkan pemanggilannya membawa block. Compiler menerima pemanggilan ini karena kehadiran yield di dalam tubuh metode menyatakan bahwa metode tersebut dirancang untuk dipanggil dengan block. Baris puts "Piket pagi" berada di dalam block, sehingga setiap yield menjalankannya satu kali dan keluarannya muncul dua kali. Urutan keluaran mengikuti urutan yield, bukan urutan penulisan block. Pemanggilan yield tanpa argumen digunakan pada contoh ini karena blocknya tidak membutuhkan nilai apa pun.
yield sebagai titik pemanggilan#
yield bukan sekadar penanda, melainkan ekspresi yang dievaluasi ketika metode berjalan. Bentuk yield(nilai) meneruskan argumen ke parameter block, yaitu variabel yang ditulis di antara dua garis tegak seperti |n|, dan nilai ekspresi terakhir di tubuh block menjadi nilai dari ekspresi yield itu sendiri. Dengan dua arah ini, metode mengirim data ke block dan menerima hasil kembali dari block.
# yield(nilai) meneruskan argumen dan menampung nilai balik block
def catat(nilai)
hasil = yield(nilai)
puts "Block mengembalikan: #{hasil}"
end
catat(24) do |n|
puts "Nilai diterima: #{n}"
n * 2
endNilai diterima: 24
Block mengembalikan: 48Pemanggilan catat(24) menjalankan metode dengan parameter nilai, lalu yield(nilai) meneruskan nilai itu ke parameter block n. Block menampilkan nilai yang diterimanya, kemudian menghitung n * 2 sebagai ekspresi terakhirnya. Nilai 48 itulah yang ditampung variabel hasil di dalam metode. Nilai balik block tidak wajib ditampung, tetapi menampungnya membuat kontrak dua arah itu terlihat pada kedua sisinya. Kontrak antara metode dan block dengan demikian jelas, yaitu yield(nilai) menyuplai daftar parameter block, sedangkan ekspresi terakhir block menyuplai nilai yield.
Mengulang pemanggilan block#
Jumlah pemanggilan block ditentukan oleh alur metode, bukan oleh block itu sendiri. yield boleh ditulis di dalam percabangan maupun perulangan, sehingga satu block dapat dijalankan berkali-kali dengan nilai yang berbeda-beda. Metode berikut menjalankan block untuk setiap elemen array dan meneruskan dua argumen sekaligus, yaitu elemennya dan nomor urutnya.
# yield berulang dengan argumen yang berbeda setiap pemanggilan
def tiap_hari
hari = ["Senin", "Selasa", "Rabu"]
i = 0
while i < hari.size
yield hari[i], i + 1
i += 1
end
end
tiap_hari do |nama, nomor|
puts "Hari ke-#{nomor}: #{nama}"
endHari ke-1: Senin
Hari ke-2: Selasa
Hari ke-3: RabuPerulangan while di tubuh tiap_hari berjalan tiga kali, dan setiap iterasi memanggil yield hari[i], i + 1. Dua argumen itu diterima parameter block nama dan nomor, sehingga block dapat memformat keduanya sekaligus. Nomor urut mulai dari satu karena i + 1 dihitung di sisi metode sebelum diteruskan, sehingga block tidak perlu menghitungnya sendiri. Pola inilah yang mendasari metode perulangan pada array seperti each dan map, yang seluruhnya mengandalkan yield di dalam tubuhnya. Dengan menulis metode seperti ini sendiri, pembagian tugas menjadi jelas, yaitu metode mengatur kapan dan dengan nilai apa block dipanggil, sedangkan block menentukan apa yang dikerjakan pada setiap pemanggilan.
Compiler menegakkan kontrak block#
Crystal memeriksa kecocokan block pada saat kompilasi. Metode yang mengandung yield wajib dipanggil dengan block, dan pemanggilan tanpa block ditolak compiler sebelum program sempat berjalan.
# Program ini ditolak compiler
def panggil
yield
end
panggilJalankan berkasnya dengan perintah berikut, lalu amati pesan errornya.
# jalankan program tanpa_block.cr
crystal run tanpa_block.crShowing last frame. Use --error-trace for full trace.
In tanpa_block.cr:7:1
7 | panggil
^------
Error: 'panggil' is expected to be invoked with a block, but no block was givenPesan error menunjuk ke pemanggilan panggil, bukan ke yield di dalam tubuhnya, karena block yang hilang terdeteksi pada situs pemanggilan. Compiler mengetahui bahwa panggil mengandung yield, sehingga setiap pemanggilannya harus menyertakan block. Kekurangan block dengan demikian tidak pernah menjadi error runtime, melainkan berhenti di tahap kompilasi.
Kecocokan yang sebaliknya juga diperiksa. Metode yang tidak mengandung yield menolak block yang diberikan kepadanya.
# Program ini ditolak compiler
def tanpa_yield
puts "Di dalam metode"
end
tanpa_yield do
puts "Baris ini tidak pernah dijalankan"
end# jalankan program block_diabaikan.cr
crystal run block_diabaikan.crShowing last frame. Use --error-trace for full trace.
In block_diabaikan.cr:7:1
7 | tanpa_yield do
^----------
Error: 'tanpa_yield' is not expected to be invoked with a block, but a block was givenMetode tanpa_yield tidak mengandung yield, sehingga block yang melekat pada pemanggilannya tidak memiliki titik pemanggilan. Alih-alih mengabaikan block itu secara diam-diam, compiler menolak programnya dan menandai baris pemanggilan. Penegakan dua arah ini membuat kontrak block terdokumentasi pada definisi metodenya, yaitu kehadiran yield menyatakan block wajib, sedangkan ketiadaannya menyatakan block tidak diterima.
Pemilihan antara do … end dan kurung kurawal#
Kedua bentuk penulisan block sama-sama diterima pada semua pemanggilan metode, sehingga pemilihannya bebas dari sisi compiler. Yang membedakan adalah konvensi keterbacaan di antara pengembang dan ikatan masing-masing bentuk pada ekspresi berantai. Keduanya dibahas berurutan pada bagian ini.
Kedua bentuk tidak persis setara dalam satu hal, yaitu ikatannya pada ekspresi berantai. Kurung kurawal melekat pada pemanggilan terkanan pada rantai ekspresi, sehingga pada baris berikut block menempel pada map dan perhitungan berjalan sesuai harapan.
# kurung kurawal menempel pada pemanggilan map
angka = [1, 2, 3]
p angka.map { |x| x * 2 }[2, 4, 6]Bentuk do ... end memiliki ikatan yang lebih longgar dan melekat pada pemanggilan terkiri pada rantai tersebut. Pada ekspresi p angka.map do ... end, block melekat pada p, bukan pada map.
# ikatan do ... end berbeda dari kurung kurawal
angka = [1, 2, 3]
p angka.map do |x|
x * 2
end# jalankan program do_end.cr
crystal run do_end.crShowing last frame. Use --error-trace for full trace.
In do_end.cr:5:9
5 | p angka.map do |x|
^--
Error: 'Array(Int32)#map' is expected to be invoked with a block, but no block was givenKarena block menempel pada p, pemanggilan map berjalan tanpa block dan compiler menolaknya dengan pesan yang menyebut Array(Int32)#map. Kekeliruan ikatan semacam ini mudah terlewat karena sintaks blocknya sendiri tidak salah, tetapi compiler Crystal menangkapnya pada saat kompilasi. Apabila tubuh block memang panjang, tulis pemanggilannya tanpa metode pembungkus di depannya, atau tampung hasilnya ke variabel terlebih dahulu sebelum ditampilkan.
Ringkasan#
- Block ditulis melekat pada satu pemanggilan metode dengan
do ... endatau kurung kurawal, danyielddi dalam tubuh metode adalah titik pemanggilannya. yield(nilai)meneruskan argumen ke parameter block, sedangkan ekspresi terakhir block menjadi nilai dari ekspresiyield.- Jumlah dan waktu pemanggilan block sepenuhnya ditentukan oleh alur metode yang memanggilnya.
- Compiler menegakkan kontrak block secara dua arah, yaitu metode dengan
yieldwajib dipanggil dengan block dan metode tanpayieldmenolak block.
Referensi#
- Referensi bahasa Crystal — Crystal Language Reference
— Dokumentasi sintaks dan semantik block serta
yield. - Dokumentasi API Crystal 1.22.1 — Dokumentasi API Crystal
— Rincian metode bawaan yang memanggil block, seperti
eachdanmappadaArray.