Fungsi Variadik

Mendefinisikan fungsi variadik dengan argumen rest dan dispatch multi-arity.

Daftar isi

Fungsi pada core seperti + dan max menerima sejumlah argumen yang tidak tetap, sedangkan defn yang biasa hanya menetapkan satu daftar parameter. Topik ini membahas dua mekanisme yang menghadirkan fleksibilitas serupa pada fungsi buatan sendiri, yaitu argumen variadic melalui simbol & dan dispatch multi-arity, beserta apply yang menjembatani koleksi ke keduanya. Setelah topik ini dibaca, fungsi seperti penjumlahan waktu tunggu antrean atau penghitungan total pesanan dapat didefinisikan dengan bentuk pemanggilan yang longgar namun tetap terkendali.

Argumen rest dengan &#

Simbol & pada daftar parameter mengumpulkan seluruh argumen sisanya menjadi satu seq. Parameter yang berada setelah & disebut argumen rest (rest argument), dan di dalam badan fungsi ia diperlakukan sebagai koleksi biasa. Fungsi yang daftar parameternya berisi & disebut fungsi variadic, yakni fungsi yang menerima banyak argumen tanpa batas jumlah yang tetap. Bagian berikut memperlihatkan bentuk dasarnya pada fungsi penjumlahan sederhana.

clojure Fungsi variadik dengan argumen rest
(defn jumlahkan [& angka]
  (apply + angka))

(println "(jumlahkan)               =" (jumlahkan))
(println "(jumlahkan 1 2 3)         =" (jumlahkan 1 2 3))
(println "(jumlahkan 5 10 15 20 25) =" (jumlahkan 5 10 15 20 25))
text Keluaran
(jumlahkan)               = 0
(jumlahkan 1 2 3)         = 6
(jumlahkan 5 10 15 20 25) = 75

Parameter angka menampung semua argumen yang dilewatkan, termasuk keadaan kosong. Pemanggilan tanpa argumen menghasilkan seq kosong, dan (apply + []) tetap sah karena + memiliki arity tanpa argumen berupa 0. Di dalam badan fungsi, angka tidak dapat diteruskan langsung ke + sebagai satu koleksi, karena + mengharapkan angka yang terpisah. Karena itu apply membukanya menjadi argumen posisi satu per satu. Pola (apply f koleksi) seperti ini adalah bentuk badan fungsi variadik yang paling umum ditemui.

Arity minimum pada fungsi variadik#

Variadik murni seperti jumlahkan menerima nol argumen sekalipun. Apabila minimal satu argumen harus ada, satu parameter tetap ditulis sebelum &, sehingga daftar parameternya berbentuk [pertama & sisanya]. Bentuk ini menjamin pemanggilan tanpa argumen tidak pernah terjadi, sedangkan sisa argumen tetap terkumpul sebagai seq pada sisanya. Nilai yang diikat parameter tetap tersebut juga dapat menjadi acuan bagi logika di dalam badan fungsi.

clojure Variadik dengan arity minimum satu
;; arity minimum satu: [pertama & sisanya]
(defn total-menunggu
  [pertama & sisanya]
  (apply + pertama sisanya))

(println "(total-menunggu 4)      =" (total-menunggu 4))
(println "(total-menunggu 4 6 10) =" (total-menunggu 4 6 10))
(println "apply atas vector       =" (apply total-menunggu [4 6 10]))
text Keluaran
(total-menunggu 4)      = 4
(total-menunggu 4 6 10) = 20
apply atas vector       = 20

total-menunggu menjumlahkan waktu tunggu loket dengan jaminan terdapat setidaknya satu nilai. Parameter pertama mengikat argumen wajib, sisanya menampung sisanya, dan keduanya diteruskan bersama ke apply. Baris terakhir memperlihatkan peran apply sebagai jembatan dua arah, yaitu koleksi [4 6 10] yang tersimpan di suatu tempat dapat diteruskan ke fungsi variadik seolah-olah argumennya ditulis terpisah di tempat pemanggilan.

Dispatch multi-arity#

Mekanisme kedua adalah multi-arity, yaitu satu nama fungsi dengan beberapa daftar parameter yang masing-masing memiliki badannya sendiri. defn menerima beberapa daftar parameter bertingkat, dan pada saat pemanggilan, Clojure memilih badan yang jumlah parameternya cocok dengan jumlah argumen. Pemilihan ini disebut dispatch berdasarkan arity, dan seluruh daftar parameter harus ditulis dalam satu defn yang sama agar fungsi tetap satu kesatuan.

clojure Total pesanan dengan dua arity
;; multi-arity: satu nama, beberapa daftar parameter
(defn total
  ([jumlah]
   jumlah)
  ([jumlah potongan-persen]
   (- jumlah (quot (* jumlah potongan-persen) 100))))

(println "(total 50000)     =" (total 50000))
(println "(total 50000 20)  =" (total 50000 20))
(println "(total 120000 10) =" (total 120000 10))
text Keluaran
(total 50000)     = 50000
(total 50000 20)  = 40000
(total 120000 10) = 108000

Arity satu argumen mengembalikan nilai pesanan apa adanya, sedangkan arity dua argumen menerapkan potongan dalam persen. quot digunakan agar hasilnya tetap bilangan bulat yang tepat tanpa bagian desimal. Dispatch di sini bukan nilai yang dihitung dari isi argumen, melainkan pemilihan badan berdasarkan jumlah argumen, sehingga kedua versi perilaku berada di satu nama yang sama dan terbaca berdampingan dalam satu definisi.

Delegasi antar arity#

Arity yang lebih sederhana lazim mendelegasikan pekerjaannya ke arity yang lebih umum agar logika tidak ditulis dua kali. Delegasi dilakukan dengan memanggil nama fungsinya sendiri menggunakan jumlah argumen arity sasaran. Pola ini menjaga satu sumber kebenaran, sebab perubahan pada arity umum otomatis berlaku pula pada seluruh arity yang mendelegasikan kepadanya.

clojure Delegasi dari arity sederhana ke arity umum
;; arity tetap mendelegasikan ke arity yang lebih umum
(defn catat
  ([pesan]
   (catat pesan :info))
  ([pesan tingkat]
   (str "[" (name tingkat) "] " pesan)))

(println (catat "loket 2 dibuka"))
(println (catat "antrean melebihi 20" :peringatan))
text Keluaran
[info] loket 2 dibuka
[peringatan] antrean melebihi 20

Arity satu argumen hanya menyediakan :info sebagai tingkat catatan, lalu memanggil arity dua argumen. Logika pembentukan string dengan demikian hanya ada di satu tempat. Pola serupa digunakan pada fungsi variadik yang perlu menormalisasi argumennya terlebih dahulu, misalnya arity variadik yang mengumpulkan argumen lalu mendelegasikannya ke arity tetap yang menerima koleksi.

Kesalahan umum#

Kesalahan pertama berupa lebih dari satu arity variadik dalam satu defn, atau arity variadik yang tidak berada paling akhir. Definisi demikian ditolak oleh compiler pada saat evaluasi, sehingga tidak ada keluaran yang bisa diperiksa. Perbaikannya berupa penyusunan ulang parameter, yaitu satu arity variadik yang selalu terakhir, dengan arity tetap lainnya di depannya.

Kesalahan kedua berupa koleksi yang diteruskan tanpa apply. Ekspresi (+ angka) dengan angka berupa seq memicu ClassCastException karena + mengharapkan angka, bukan koleksi. Badan variadik yang menjumlahkan selalu membutuhkan (apply + angka) agar isi seq dibuka menjadi argumen posisi.

Kesalahan ketiga berupa pemanggilan multi-arity dengan jumlah argumen yang tidak terdaftar pada daftar parameter mana pun. Pemanggilan demikian memicu ArityException pada saat berjalan. Perbaikannya berupa penambahan arity yang hilang pada defn, atau penggabungan dengan arity variadik apabila jumlah argumen memang dirancang bebas.

Ringkasan#

  • & mengumpulkan argumen sisa menjadi satu seq, dan fungsi dengan daftar parameter demikian disebut variadic.
  • Parameter tetap yang ditulis sebelum & menetapkan arity minimum fungsi variadik.
  • Multi-arity pada defn mendispatch badan fungsi berdasarkan jumlah argumen, dengan seluruh arity dalam satu definisi.
  • apply membuka koleksi menjadi argumen posisi dan menjadi jembatan menuju fungsi variadik maupun multi-arity.

Referensi#