Blok (block) adalah unit kode yang diserahkan kepada metode untuk dijalankan melalui yield, tetapi blok tidak memiliki wujud objek sehingga tidak dapat disimpan dalam variabel. Kelas Proc menyediakan wadah itu, yaitu objek yang membungkus kode blok agar dapat disimpan, dipindahkan, dan dipanggil ulang kapan pun dibutuhkan. Topik ini membahas pembuatan objek Proc melalui Proc.new dan proc, pemanggilannya dengan .call, konversi dua arah antara blok dan Proc dengan tanda &, sifat Proc menangkap lingkungan sekitarnya (closure), pembacaan jumlah argumen melalui arity, serta kondisi ketika Proc menjadi pilihan yang tepat.
Blok menjadi objek Proc#
Blok hanya hidup selama pemanggilan metode yang membawanya, dan setelah metode selesai blok tidak dapat dijangkau lagi. Kelas Proc mengatasi keterbatasan tersebut dengan membungkus kode blok menjadi objek yang tersimpan dalam variabel. Objek Proc yang baru dibuat belum menjalankan apa pun, melainkan hanya menyimpan kodenya. Eksekusi baru terjadi ketika metode call dipanggil, dan setiap pemanggilan call menjalankan kode yang sama dari awal.
# Block disimpan menjadi objek Proc
selamat = Proc.new { |nama| "Selamat datang, #{nama}!" }
dua_kali = proc { |angka| angka * 2 }
puts selamat.class
puts selamat.call("Citra")
puts dua_kali.call(21)Proc
Selamat datang, Citra!
42Pada program tersebut, variabel selamat memegang objek kelas Proc yang berisi satu baris interpolasi, sedangkan dua_kali memegang proc yang mengalikan angkanya. Nilai yang diserahkan kepada call mengisi parameter blok, sehingga call("Citra") menghasilkan kalimat sapaan untuk Citra. Kedua cara pembuatan pada contoh, yaitu Proc.new { } dan proc { }, menghasilkan objek yang identik, sehingga pemilihan di antara keduanya menjadi kebiasaan gaya.
Memanggil Proc dengan call#
Proc yang tersimpan tidak berjalan dengan sendirinya, dan eksekusinya baru terjadi ketika Proc itu dipanggil. Metode call menjalankan kode yang dibungkus Proc, meneruskan argumen ke parameternya, lalu mengembalikan nilai ekspresi terakhir di dalam badannya. Ruby juga menyediakan dua bentuk pemanggilan lain yang berperilaku sama, yaitu jumlahkan.(10, 4) dan jumlahkan[10, 4], tetapi .call digunakan karena bentuknya paling eksplisit dan paling mudah dikenali.
# Tiga bentuk pemanggilan Proc yang setara
jumlahkan = proc { |a, b| a + b }
puts jumlahkan.call(10, 4)
puts jumlahkan.(10, 4)
puts jumlahkan[10, 4]14
14
14Ketiga baris pemanggilan menghasilkan 14 karena ketiganya menjalankan proc yang sama dengan sepasang argumen yang identik. Argumen pertama mengisi a dan argumen kedua mengisi b, sebagaimana argumen metode mengisi parameter metode. Nilai kembaliannya adalah ekspresi terakhir badan proc, sehingga { |a, b| a + b } mengembalikan hasil penjumlahannya tanpa pernyataan return. Jumlah argumen pada call tidak harus persis sama dengan jumlah parameternya, dan perilaku ini dibahas pada bagian arity di bawah.
Konversi blok dan Proc dengan tanda &#
Tanda & bekerja dua arah antara blok dan Proc. Ditempatkan pada parameter terakhir sebuah metode, tanda & menangkap blok yang menyertai pemanggilan dan menyimpannya sebagai objek Proc. Ditempatkan pada argumen pemanggilan, tanda & mengubah Proc menjadi blok, sehingga Proc yang tersimpan dapat diserahkan kepada metode penerima blok mana pun.
# Tanda & mengubah dua arah antara block dan Proc
def catat(&proses)
puts proses.class
proses.call("Ali")
end
puts catat { |nama| "Halo, #{nama}!" }
kuadrat = proc { |n| n * n }
p [1, 2, 3].map(&kuadrat)Proc
Halo, Ali!
[1, 4, 9]Parameter &proses pada metode catat menangkap blok pemanggilan menjadi objek Proc, yang kemudian diperiksa kelasnya dan dipanggil dengan call. Arah sebaliknya terjadi pada map(&kuadrat), yaitu Proc yang tersimpan diubah kembali menjadi blok agar diterima map. Konversi ini menghubungkan objek Proc dengan metode iterator pustaka standar seperti map dan select.
Proc menangkap lingkungan sekitarnya#
Proc yang dibuat di dalam metode membawa serta variabel lokal yang hidup pada saat Proc itu dibuat. Variabel tersebut tetap tersimpan di dalam Proc bahkan setelah metode pembuatnya selesai berjalan. Sifat ini disebut closure, yaitu kemampuan sebuah Proc mengingat lingkungan tempat Proc itu dibuat. Karena itu, Proc dapat dipanggil di tempat lain dan tetap mengakses nilai yang dibawanya.
# Proc menangkap variabel lingkungannya saat dibuat
def buat_pengali(faktor)
proc { |angka| angka * faktor }
end
kali_dua = buat_pengali(2)
kali_sepuluh = buat_pengali(10)
puts kali_dua.call(7)
puts kali_sepuluh.call(7)
# Dua Proc yang dibuat berdampingan berbagi variabel yang sama
def buat_penghitung
jumlah = 0
naik = proc { jumlah += 1 }
baca = proc { jumlah }
[naik, baca]
end
naik, baca = buat_penghitung
naik.call
naik.call
naik.call
puts baca.call14
70
3Parameter faktor milik metode buat_pengali tetap hidup di dalam proc yang dibuatnya, sehingga kali_dua selalu mengalikan dengan dua dan kali_sepuluh mengalikan dengan sepuluh, bahkan setelah metode pembuatnya selesai berjalan. Apabila buat_pengali dipanggil lagi dengan faktor lain, Proc baru yang dihasilkan menangkap lingkungannya sendiri, dan proc yang lama tidak terpengaruh. Pada buat_penghitung, dua proc dibuat berdampingan dan berbagi variabel jumlah yang sama, sehingga setelah naik dipanggil tiga kali, baca mengembalikan nilai tiga. Lingkungan yang ditangkap bukan salinan nilai saat pembuatan, melainkan variabel yang sama dan terus hidup.
Membaca jumlah argumen dengan arity#
Metode arity mengembalikan jumlah argumen yang diharapkan sebuah Proc, yaitu jumlah parameter yang dinyatakan di antara dua garis vertikal. Nilainya negatif apabila parameternya berbentuk *daftar yang menampung jumlah nilai yang tidak tetap. Proc bersikap longgar terhadap jumlah argumen, yaitu mengisi parameter yang kekurangan nilai dengan nil dan membuang nilai yang berlebih tanpa pesan. Metode ini jarang dipanggil pada kode sehari-hari, tetapi berguna ketika kode menerima Proc dari luar dan perlu menyesuaikan cara memanggilnya dengan parameternya.
# arity menyatakan jumlah argumen yang diharapkan Proc
satu = proc { |x| x }
dua = proc { |x, y| [x, y] }
banyak = proc { |*daftar| daftar }
puts satu.arity
puts dua.arity
puts banyak.arity
# Proc bersikap longgar terhadap jumlah argumen
p dua.call(10)
p dua.call(10, 20, 30)1
2
-1
[10, nil]
[10, 20]Tiga pemanggilan arity pada contoh mengembalikan 1, 2, dan -1 sesuai parameternya, sedangkan nilai -1 menandakan proc yang menerima sejumlah nilai sekaligus. Pemanggilan dua.call(10) menghasilkan [10, nil] karena parameter y tidak mendapat nilai, sedangkan dua.call(10, 20, 30) tetap menghasilkan [10, 20] karena nilai ketiga dibuang. Sifat longgar ini membuat pemanggilan proc nyaris tidak pernah gagal karena salah menghitung jumlah argumen, dan menjadi salah satu pembeda utama antara proc dan lambda.
Kapan Proc digunakan#
Proc menjadi pilihan yang tepat ketika perilaku perlu disimpan, dipindahkan, atau dipilih saat program berjalan. Contoh yang lazim adalah menyimpan beberapa transformasi dalam satu struktur data, meneruskan perilaku sebagai argumen kepada metode lain, atau menyimpan langkah eksekusi untuk dipanggil nanti, misalnya sebagai callback, yaitu kode yang dipanggil kembali ketika peristiwa tertentu terjadi. Blok tidak dapat menjalankan tugas-tugas itu karena blok tidak berwujud objek, sedangkan Proc dapat diletakkan di dalam array, hash, atau variabel apa pun.
# Perilaku disimpan dalam hash dan dipilih saat dijalankan
nilai_ujian = [78, 90, 65, 85]
operasi = {
"terendah" => proc { |n| n.min },
"tertinggi" => proc { |n| n.max },
"rata-rata" => proc { |n| n.sum / n.length.to_f }
}
operasi.each do |nama, hitung|
puts "#{nama}: #{hitung.call(nilai_ujian)}"
endterendah: 65
tertinggi: 90
rata-rata: 79.5Hash operasi memetakan nama operasi kepada proc yang melaksanakannya, dan pemanggilan memilih operasi melalui nama yang tersimpan. Nilai rata-rata dihitung dengan pembagian pecahan melalui to_f, sehingga 79.5 muncul sebagai angka desimal dan bukan bilangan bulat. Menambahkan jenis operasi baru berarti menambahkan satu entri hash tanpa menyentuh perulangan yang mencetak laporannya. Pola ini tidak dapat dibangun dengan blok yang diserahkan langsung, karena blok tidak dapat disimpan sebagai entri hash.
Kesalahan umum#
Kesalahan yang sering terjadi adalah menyerahkan objek Proc kepada metode penerima blok tanpa tanda &. Tanpa tanda tersebut, Proc diperlakukan sebagai argumen biasa, dan metode seperti map yang tidak menerima argumen menolaknya dengan error. Kesalahan serupa terjadi pada each, select, dan metode iterator lain dengan pesan yang senada. Kasus berikut diperagakan dengan pernyataan begin ... rescue yang menangkap error di dalam kode, sehingga program tetap berakhir normal dan pesannya tercetak.
# Menyerahkan Proc kepada map tanpa tanda &
kuadrat = proc { |n| n * n }
begin
p [1, 2, 3].map(kuadrat)
rescue => e
puts e.class
puts e.message
end
p [1, 2, 3].map(&kuadrat)ArgumentError
wrong number of arguments (given 1, expected 0)
[1, 4, 9]Pesan wrong number of arguments (given 1, expected 0) menyatakan bahwa map tidak menerima argumen posisi apa pun, sedangkan satu argumen berupa proc justru diserahkan kepadanya. Perbaikannya adalah menambahkan tanda & di depan proc, sehingga proc dikonversi menjadi blok dan map menjalankannya untuk setiap elemen. Baris terakhir pada contoh menunjukkan hasil yang benar setelah tanda & dipasang.
Ringkasan#
Proc.new { }danproc { }membungkus blok menjadi objek Proc yang dapat disimpan dalam variabel dan dipanggil ulang kapan pun.- Metode
.callmenjalankan Proc dan meneruskan argumennya ke parameter blok, denganproc.()danproc[]sebagai padanan yang setara. - Tanda
&mengonversi dua arah, yaitu&prosespada parameter metode menangkap blok menjadi Proc, sedangkan&prosespada pemanggilan menyerahkan Proc sebagai blok. - Proc adalah closure yang menyimpan variabel lingkungannya setelah metode pembuatnya selesai, sedangkan
aritymenyatakan jumlah argumen yang diharapkan.
Referensi#
- Proc — Dokumentasi Ruby 3.4
— Dokumentasi kelas Proc mencakup pembuatan objek, pemanggilan
call, danarity. - Memanggil Metode — Dokumentasi Ruby 3.4
— Bagian Block Argument menjelaskan tanda
&pada penyampaian blok dan konversi Proc.