Kode sebagai Data, Sekali Lagi

Memahami quoting, syntax-quote, unquote, dan unquote-splicing pada kode sebagai data.

Daftar isi

Seluruh kode Clojure berupa data, dan sifat inilah yang memungkinkan macro. Quote telah diperkenalkan pada modul Bentuk Bahasa sebagai cara menahan evaluasi sebuah form. Topik ini memperdalam sisi tersebut melalui empat pembacaan, yaitu quote ', syntax-quote `, unquote ~, dan unquote-splicing ~@. Dengan keempat pembacaan tersebut, form dapat diperlakukan sebagai struktur data biasa yang diseksi dengan fungsi koleksi, kemampuan yang menjadi bekal langsung penulisan macro.

Quote: form menjadi data#

Tanda kutip tunggal ' merupakan reader macro yang setara dengan form quote. Pembacaan '(+ 1 2) menghasilkan (quote (+ 1 2)), dan evaluasinya mengembalikan form itu apa adanya sebagai data. Tidak ada fungsi yang dipanggil, sehingga simbol + pada form terkutip hanyalah simbol, bukan fungsi yang siap dieksekusi.

Form terkutip kemudian diperlakukan sebagai koleksi biasa. first mengambil elemen pertama form, rest mengambil sisa elemennya, count menghitung jumlah elemen, dan type mengungkap kelas konkretnya. Blok berikut menerapkan fungsi-fungsi tersebut atas form yang sama, dan baris pertamanya mencetak form utuh sebagai pembanding.

clojure Membedah form terkutip dengan fungsi koleksi
(prn '(+ 1 2))
(prn (first '(+ 1 2)))
(prn (rest '(+ 1 2)))
(prn (count '(+ 1 2)))
(prn (type '(+ 1 2)))
text Keluaran
(+ 1 2)
+
(1 2)
3
clojure.lang.PersistentList

Keluaran memperlihatkan bahwa '(+ 1 2) tetap berupa list berisi tiga elemen, yaitu simbol + beserta dua angka. Fungsi koleksi bekerja tanpa penyesuaian apa pun, dan type menunjukkan kelas clojure.lang.PersistentList. Pada posisi inilah macro bekerja, karena argumen yang diterima sebuah macro berupa data yang berwujud persis seperti form terkutip di atas.

Syntax-quote dan resolusi simbol#

Syntax-quote ditulis dengan karakter kutip terbalik ` dan berperan utama dalam penulisan macro. Struktur formnya diperlakukan sebagaimana quote, tetapi setiap simbol di dalamnya diselesaikan terhadap lingkungan tempat form itu ditulis. Simbol yang menunjuk var bawaan diberi awalan namespace lengkap, sedangkan simbol yang tidak terikat apa pun diberi awalan namespace saat ini.

clojure Resolusi simbol pada syntax-quote
(prn `(+ 1 2))
(prn `(inc x))
(prn `[inc x])
text Keluaran
(clojure.core/+ 1 2)
(clojure.core/inc user/x)
[clojure.core/inc user/x]

Keluaran menunjukkan + dan inc menjadi clojure.core/+ dan clojure.core/inc karena keduanya menunjuk var pada namespace clojure.core. Simbol x yang tidak menunjuk apa pun menjadi user/x, mengikuti namespace tempat form dituliskan. Awalan lengkap ini penting ketika form hasil digunakan dari namespace lain, karena rujukan var tetap benar tanpa terpengaruh nama lokal pada tempat pemanggilan, jaminan yang tidak diberikan quote biasa.

Unquote: menyuntik nilai#

Unquote ditulis dengan ~ dan hanya sah di dalam syntax-quote. Tugasnya menyuntikkan nilai: ekspresi yang mengikutinya dievaluasi pada saat form disusun, lalu hasilnya menggantikan posisi ekspresi tersebut. Tanpa unquote, seluruh isi syntax-quote tetap berupa data, termasuk ekspresi yang seharusnya dihitung.

clojure Menyuntikkan nilai ke dalam form syntax-quote
(def nilai 5)
(prn `(inc ~nilai))
(prn `(tambah ~(+ 1 2) selesai))
text Keluaran
(clojure.core/inc 5)
(user/tambah 3 user/selesai)

Pada pemanggilan pertama, ~nilai menyuntikkan angka 5 sehingga simbol nilai digantikan angka tersebut. Pada pemanggilan kedua, ~(+ 1 2) dihitung lebih dahulu dan menyisakan angka 3 pada form. Simbol lain yang tidak diunquote mempertahankan resolusinya, yaitu user/tambah dan user/selesai. Pemisahan antara bagian yang tetap data dan bagian yang dihitung inilah mekanisme inti ketika templat kode dipadukan dengan nilai.

Unquote-splicing: membuka koleksi#

Unquote-splicing ditulis dengan ~@ dan membuka sebuah koleksi sehingga elemen-elemennya masuk sebagai elemen terpisah pada form tujuan. Perbedaannya dengan unquote biasa terlihat ketika nilai yang disisipkan berupa koleksi: ~ menyisipkan koleksi sebagai satu elemen, sedangkan ~@ menyisipkan isi koleksi satu per satu.

clojure Perbandingan unquote dan unquote-splicing atas sebuah vector
(def angka [2 3 4])
(prn `(f ~angka))
(prn `(f ~@angka))
text Keluaran
(user/f [2 3 4])
(user/f 2 3 4)

Keluaran memperlihatkan kontras tersebut secara langsung. ~angka menyisipkan vector [2 3 4] sebagai satu argumen, sedangkan ~@angka menyisipkan angka 2, 3, dan 4 sebagai tiga argumen terpisah. Bentuk kedua menjadi pilihan ketika macro perlu meneruskan sejumlah argumen tanpa jumlah tetap, misalnya meneruskan seluruh isi list sebagai badan sebuah fungsi.

Empat pembacaan kode sebagai data#

Keempat pembacaan di atas saling melengkapi. Quote menahan evaluasi sepenuhnya, syntax-quote menahan evaluasi sambil menyelesaikan simbol, sedangkan unquote dan unquote-splicing membuka celah agar nilai dapat masuk ke dalam templat. Tabel berikut menyandingkan perilaku keempatnya.

PembacaanDitulisPerilaku
quote'Form dikembalikan apa adanya sebagai data, tanpa evaluasi dan tanpa resolusi simbol.
syntax-quote`Form dikembalikan sebagai data dengan setiap simbol diselesaikan penuh.
unquote~Nilai ekspresi disuntikkan menggantikan posisi ekspresinya di dalam syntax-quote.
unquote-splicing~@Koleksi dibuka sehingga elemennya masuk sebagai elemen terpisah di dalam syntax-quote.

Dua pembacaan pertama bersifat mandiri dan dapat digunakan langsung di REPL maupun pada data literal, sedangkan dua pembacaan terakhir selalu berpasangan dengan syntax-quote. Pada penulisan macro, pola yang berulang terdiri atas syntax-quote sebagai templat, unquote untuk nilai tunggal, dan unquote-splicing untuk deretan argumen tanpa jumlah tetap. Pola inilah yang dipraktikkan ketika macro ditulis dengan defmacro.

Susunan ini juga dapat bersarang, misalnya unquote yang menampung ekspresi berisi syntax-quote lain, sehingga satu templat menghasilkan templat baru. Kebutuhan bersarang seperti ini jarang muncul, dan satu tingkat syntax-quote dengan unquote pada posisi yang tepat sudah mencukupi untuk macro pada umumnya. Sifat kode sebagai data juga tidak berhenti di sini, karena fungsi seperti eval dapat mengevaluasi kembali data yang disusun, tetapi pembahasannya berada di luar cakupan topik ini.

Ringkasan#

  • ' mengembalikan form sebagai data tanpa evaluasi, dan form terkutip diseksi dengan first, rest, serta count.
  • ` berperilaku seperti quote tetapi menyelesaikan setiap simbol hingga berawalan namespace penuh.
  • ~ menyuntikkan nilai ekspresi pada posisinya di dalam syntax-quote.
  • ~@ membuka koleksi sehingga elemennya masuk sebagai elemen terpisah.

Referensi#