Destructuring

Mengikat nilai secara sekuensial dan asosiatif dengan destructuring, :as, dan default :or.

Daftar isi

Destructuring adalah bentuk binding, yaitu pengikatan nilai ke nama, yang mengikat elemen sebuah koleksi langsung ke nama lokal sehingga akses berulang dengan get atau keyword tidak diperlukan. Topik ini membahas binding sekuensial pada vector, binding asosiatif pada map, nilai default dengan :or, rujukan ke koleksi utuh dengan :as, serta pola bersarang pada let dan parameter fungsi. Contoh yang digunakan berupa data nilai ujian dan pesanan sederhana, sedangkan seluruh blok dijalankan sebagai skrip dengan clojure -M, sehingga setiap nilai dicetak dengan println dan keluarannya dapat direproduksi.

Binding sekuensial pada vector#

Pola vector mengikat elemen berdasarkan posisi. Setiap simbol yang dituliskan pada pola terikat pada elemen koleksi di posisi yang sama, dan jumlah simbol boleh lebih sedikit daripada jumlah elemen karena elemen berlebih cukup diabaikan. Untuk posisi yang tidak diperlukan, konvensinya adalah menuliskan simbol garis bawah _ sebagai nama, sebagaimana terlihat pada blok berikut.

clojure Binding posisi, pengabaian, dan sisa elemen
(let [[poin-a poin-b] [88 92]]
  (println "Poin A:" poin-a)
  (println "Poin B:" poin-b))

(let [[_ _ ketiga] [10 20 30]]
  (println "Nilai ketiga:" ketiga))

(let [[awal & sisanya] [5 6 7 8 9]]
  (println "Awal:" awal)
  (println "Sisanya:" sisanya))
text Keluaran
Poin A: 88
Poin B: 92
Nilai ketiga: 30
Awal: 5
Sisanya: (6 7 8 9)

Simbol & mengikat sisa elemen menjadi list, bukan vector, karena mekanisme destructuring bekerja di atas abstraksi seq. Apabila pola lebih panjang daripada koleksinya, simbol yang tidak mendapatkan elemen terikat pada nil tanpa pesan error apa pun. Perilaku ini menegaskan bahwa pola sebaiknya mencerminkan bentuk data yang benar-benar diharapkan, bukan sekadar jumlah posisi yang sedang tersedia.

Destructuring pada parameter fungsi#

Parameter fungsi boleh berupa pola destructuring apa pun, sehingga bentuk argumen yang diharapkan terbaca langsung dari tanda tangan fungsi. Pemanggilan memetakan argumen ke pola tersebut sebelum badan fungsi dievaluasi, dan polanya boleh bersarang mengikuti struktur data. Fungsi jarak berikut menerima dua titik yang masing-masing berupa vector [x y], lalu pola bersarangnya membongkar kedua pasangan koordinat sekaligus.

clojure Pola bersarang pada parameter fungsi
(defn jarak
  [[[x1 y1] [x2 y2]]]
  (let [dx (- x2 x1)
        dy (- y2 y1)]
    (Math/sqrt (+ (* dx dx) (* dy dy)))))

(println "Jarak:" (jarak [[0 0] [3 4]]))
text Keluaran
Jarak: 5.0

Nilai 5.0 berasal dari tripel Pythagoras 3-4-5, dan tanda desimal menunjukkan Math/sqrt mengembalikan double. Tanpa destructuring, badan fungsi harus mengambil keempat koordinat dengan nth, sehingga maksud fungsi tertutup oleh akses elemen. Dengan destructuring, nama x1, y1, x2, dan y2 tersedia sejak baris pertama, dan struktur input terdokumentasi pada polanya.

Binding asosiatif dengan :keys, :or, dan :as#

Pola map mengikat nilai berdasarkan kunci, bukan posisi, sehingga urutan kunci pada map tidak berpengaruh. Direktif :keys mengikat simbol yang namanya sama dengan keyword-nya, misalnya {:keys [nama nilai]} mengikat nama dari kunci :nama dan nilai dari kunci :nilai. Padanannya tersedia untuk kunci berupa string dan symbol, yaitu :strs dan :syms, meskipun keyword tetap menjadi pilihan utama pada data Clojure. Nilai default dinyatakan dengan :or, sedangkan :as mengikat seluruh map ke satu nama.

clojure Laporan nilai ujian dengan :keys, :or, dan :as
(defn laporkan
  [{:keys [nama mapel nilai] :or {nilai 0} :as data}]
  (println "Nama  :" nama)
  (println "Mapel :" (or mapel "Umum"))
  (println "Nilai :" nilai)
  (println "Data  :" data))

(laporkan {:nama "Budi" :mapel "Matematika" :nilai 88})
(println "---")
(laporkan {:nama "Citra"})
text Keluaran
Nama  : Budi
Mapel : Matematika
Nilai : 88
Data  : {:nama Budi, :mapel Matematika, :nilai 88}
---
Nama  : Citra
Mapel : Umum
Nilai : 0
Data  : {:nama Citra}

Pemanggilan pertama membawa ketiga kunci sehingga semua nama terisi. Pemanggilan kedua hanya membawa :nama, sehingga nilai menggunakan default 0 dari :or, sedangkan mapel diberi teks "Umum" oleh ekspresi or di dalam badan fungsi. Keduanya berbeda peran, yaitu :or bekerja pada binding, sedangkan or menggantikan nilai saat evaluasi berjalan.

Destructuring bersarang#

Pola destructuring disusun bersarang mengikuti struktur data yang dibongkar, baik map di dalam map maupun vector di dalam map. Contoh berikut membaca :kode dan map :pelanggan dari satu pola tunggal, sedangkan vector :butir dibaca pada pasangan binding kedua.

clojure Membaca pesanan dengan pola bersarang
(def pesanan
  {:kode "ORD-104"
   :pelanggan {:nama "Dewi" :kota "Bandung"}
   :butir [{:produk "Buku" :jumlah 2}
           {:produk "Pulpen" :jumlah 5}]})

(let [{:keys [kode]
       {:keys [nama kota]} :pelanggan} pesanan
      [{:keys [produk]} _] (:butir pesanan)]
  (println "Kode   :" kode)
  (println "Nama   :" nama)
  (println "Kota   :" kota)
  (println "Butir 1:" produk))
text Keluaran
Kode   : ORD-104
Nama   : Dewi
Kota   : Bandung
Butir 1: Buku

Entri {:keys [nama kota]} :pelanggan pada pola map berarti nilai kunci :pelanggan dibongkar dengan pola :keys miliknya sendiri. Pasangan binding kedua memperlihatkan bentuk bertahap, yaitu (:butir pesanan) diambil sebagai sumber, lalu elemen pertamanya dibongkar dengan pola vector. Pendekatan bertahap seperti ini lebih mudah diurai daripada satu pola yang menumpuk semua tingkat, sehingga kedalaman pola dalam satu binding sebaiknya dibatasi dua tingkat saja.

Kesalahan umum#

Kesalahan yang paling sering terjadi adalah pola yang tidak cocok dengan data. Destructuring tidak memeriksa tipe sama sekali, sehingga binding terhadap data yang bentuknya salah menghasilkan nil secara senyap alih-alih memunculkan pesan error.

Kesalahan kedua terletak pada isi :or. Default dituliskan dengan nama binding, bukan dengan keyword sumbernya, karena pola {:keys [umum] :or {umum 0}} mengikat simbol umum dan defaultnya juga ditujukan pada simbol yang sama. Menuliskan :or {:umum 0} membuat default tidak pernah digunakan. Kesalahan ketiga berupa shadowing, yaitu nama binding yang menutupi nama luar dengan makna berbeda, sehingga dua nilai bercampur dalam satu fungsi tanpa peringatan apa pun. Nama yang sudah digunakan pada lingkup luar sebaiknya tidak digunakan ulang pada pola, dan nama binding perlu dipilih agar menggambarkan isinya.

Ringkasan#

  • Binding sekuensial mengikat elemen vector berdasarkan posisi, dengan & untuk sisa elemen dan garis bawah untuk posisi yang diabaikan.
  • Binding asosiatif mengikat nilai map berdasarkan kunci melalui :keys, dengan default :or yang berlaku ketika kunci tidak ada dan :as yang mengikat map utuhnya.
  • Destructuring dapat ditempatkan langsung pada parameter fungsi dan dapat disusun bersarang mengikuti struktur data.
  • Pola yang tidak cocok dengan data menghasilkan binding nil secara senyap, sehingga bentuk pola perlu dicocokkan dengan bentuk data.

Referensi#