Topik ini membahas penamaan nilai di Clojure melalui def dan let, beserta aturan lingkup yang menentukan bagian program tempat sebuah nama dikenal. def menamai nilai pada var di namespace, sedangkan let mengikat nama secara lokal pada satu blok. Topik ini juga membahas lingkup leksikal dan shadowing, yaitu pembayangan nama pada let bersarang. Setelah topik ini selesai, penamaan nilai dapat dipilih antara var global dan ikatan lokal dengan alasan yang tepat.
Menamai nilai dengan def#
Bentuk khusus def mengaitkan sebuah nama dengan nilai pada var di namespace saat ini, dan nama itu tersedia untuk seluruh bagian berkas setelah barisnya dievaluasi. Nama mengikuti konvensi kebab-case, yaitu huruf kecil yang dipisahkan tanda hubung, seperti harga-tiket. Var menampung nilai yang immutable, sehingga def lebih tepat dibaca sebagai pemberian nama tetap alih-alih pengisian variabel yang berubah-ubah.
; def menamai nilai pada var di namespace saat ini
(def harga-tiket 25000)
(def jumlah-tiket 4)
(println harga-tiket)
(println (* harga-tiket jumlah-tiket))
(println (resolve 'harga-tiket))25000
100000
#'user/harga-tiketFungsi resolve memperlihatkan var yang dirujuk sebuah symbol, dan keluaran #'user/harga-tiket menunjukkan lokasi var tersebut pada namespace user. Tanda #' pada keluaran adalah cara Clojure menuliskan sebuah var. Konvensi penamaan pada komunitas Clojure menggunakan huruf kecil dan tanda hubung untuk hampir semua nama, sedangkan huruf kapital digunakan terbatas pada konstanta khusus. Nama yang bermakna menggantikan kebutuhan komentar, karena harga-tiket sudah menyatakan isinya tanpa penjelasan tambahan.
Mengikat nilai lokal dengan let#
Bentuk let mengikat nama secara lokal dengan menerima vektor yang berisi pasangan nama dan ekspresi sumbernya, diikuti isi blok yang menggunakan nama-nama tersebut. Ikatan diproses berurutan, sehingga ikatan berikutnya boleh menggunakan ikatan sebelumnya, sebagaimana luas yang menggunakan sisi pada contoh. Seluruh ikatan hanya dikenal di dalam isi blok let, dan nama tersebut tidak terlihat lagi setelah blok selesai.
; let mengikat nama secara lokal pada satu blok
(let [panjang 12 lebar 5]
(println (* panjang lebar)))
; ikatan berikutnya boleh memakai ikatan sebelumnya
(let [sisi 7 keliling (* 4 sisi) luas (* sisi sisi)]
(println keliling)
(println luas))
; nama pada let tidak dikenal di luar bloknya
(let [lokal 42] (println lokal))
(println (resolve 'lokal))60
28
49
42
nilKeluaran nil dari resolve pada contoh membuktikan bahwa lokal tidak meninggalkan jejak di namespace setelah bloknya selesai. Pola let membuat dependensi antarhitungan tertulis secara eksplisit, karena setiap nilai antara memiliki nama dan urutan yang terbaca dari atas ke bawah. Vektor ikatan yang semakin panjang lazim ditata satu pasangan per baris agar mudah dibaca, dan penataan itu murni masalah tampilan karena tanda kurungnya yang menentukan struktur.
Lingkup leksikal dan shadowing#
Clojure menggunakan lingkup leksikal, yaitu lingkup yang ditentukan oleh letak teks bloknya, bukan oleh jalur eksekusi. let bersarang boleh menggunakan nama yang sama dengan ikatan terluarnya, dan praktik itu disebut shadowing karena ikatan dalam membayangi ikatan luar selama blok dalamnya berjalan. Pembayangan tidak mengubah nilai ikatan luar sama sekali, melainkan hanya menyembunyikannya pada lingkup yang lebih dalam.
(def x 10)
; let bersarang membayangi ikatan terluar
(println x)
(let [x 1]
(println x)
(let [x 100]
(println x))
(println x))
(println x)10
1
100
1
10Keluaran contoh memperlihatkan siklus lengkapnya, yaitu nilai 10 sebelum blok, 1 pada ikatan luar, 100 pada ikatan dalam, kembali 1 setelah blok dalam selesai, dan 10 di luar semuanya. Bentuk serupa yang berbeda peran adalah pengikatan ulang nama pada satu vektor let yang sama. Setiap pasangan baru dengan nama yang sama membuat ikatan berikutnya, sehingga tahapan perhitungan dapat berbagi nama tanpa menambah nama baru.
; ikatan let diproses berurutan sehingga nama boleh dipakai ulang
(let [nilai 50
nilai (* 2 nilai)
nilai (+ nilai 5)]
(println nilai))105Pola pengikatan ulang pada contoh terakhir menggantikan kebutuhan variabel penampung sementara yang biasa digunakan pada bahasa imperatif. Setiap tahap memperoleh nilai baru dari tahap sebelumnya, sedangkan nilai-nilai tahap lama tetap ada sebagai data yang terpisah. Shadowing yang terencana pada pola ini aman karena urutannya tegas, sedangkan shadowing pada let bersarang sebaiknya dibatasi, karena nama yang maknanya berubah antarblok menambah beban membaca.
Memilih antara def dan let#
Keduanya menamai nilai, tetapi lingkup dan perannya berbeda. def berada pada tingkat namespace sehingga cocok untuk konstanta dan nama yang digunakan lintas bagian, sedangkan let menampung nilai antara yang hanya relevan pada satu perhitungan. Aturan praktisnya, mulailah dengan let dan naikkan nama ke def apabila namanya memang diperlukan di luar bloknya, sehingga lingkup setiap nama tetap sekecil mungkin dan asal setiap nilai mudah dilacak.
| Aspek | def | let |
|---|---|---|
| Lingkup | namespace, tersedia di seluruh berkas | isi blok let saja |
| Masa berlaku | sampai def ulang pada REPL | selesai bersama bloknya |
| Pemakaian yang cocok | konstanta dan nama lintas bagian | nilai antara satu perhitungan |
Kebiasaan menjaga nama lokal tetap lokal membuat kebutuhan def menjadi wajar, yaitu hanya untuk nama yang memang berbagi makna di banyak tempat. Sebaliknya, membanjirkan namespace dengan def untuk nilai antara menyulitkan pelacakan asal setiap nilai dan memperbanyak peluang tabrakan nama.
Kesalahan umum#
Menggunakan ikatan let di luar bloknya. Nama yang diikat let tidak dikenal setelah blok selesai, sehingga pemanggilan di luarnya memicu Unable to resolve symbol. Perbaikannya dengan memindahkan penggunaan ke dalam blok, atau menaikkan namanya ke def apabila lingkupnya memang harus lebih luas.
Menggunakan nama sebelum ikatannya diproses. Vektor let diproses berurutan, sehingga (let [b (* a 2) a 5] ...) memicu Unable to resolve symbol: a karena a belum terikat saat b dihitung. Perbaikannya dengan menyusun ulang urutan ikatan agar setiap nama sudah terikat sebelum digunakan.
Menganggap def sebagai pengisian variabel yang dapat diubah. Var menampung nilai immutable, sehingga def ulang membuat pemetaan baru di namespace dan tidak mengubah nilai yang sudah ada. Perbaikannya dengan memperlakukan def sebagai penamaan nilai, dan menangani perubahan keadaan dengan mekanisme khusus di luar pola penamaan.
Ringkasan#
- def menamai nilai pada var di namespace saat ini dengan konvensi kebab-case.
- let mengikat nama secara lokal, berurutan, dan terbatas pada isi bloknya.
- Lingkup leksikal ditentukan letak teks, dan let bersarang membayangi ikatan luar tanpa mengubah nilainya.
- def dipilih untuk nama lintas blok, sedangkan let untuk nilai antara satu perhitungan.
Referensi#
- Vars and the Global Environment — Clojure — Dokumentasi resmi def, var, dan namespace.
- clojure.core/let — ClojureDocs — Contoh penggunaan let beserta catatan komunitasnya.
- Learn Clojure — Clojure — Panduan resmi def, let, dan penamaan.