Topik ini membahas vector dan list, dua koleksi berurutan paling mendasar di Clojure, beserta cara mengakses dan menambah elemennya. Keduanya menampung elemen heterogen, menjaga urutan, dan bersifat immutable, tetapi keduanya menempati peran yang berbeda karena struktur internal dan cara evaluasinya. Setelah topik ini selesai, pemilihan antara vector dan list dapat diambil berdasarkan peran datanya, bukan berdasarkan kebiasaan menulis.
Literal vector dan list#
Vector ditulis dalam tanda kurung siku seperti [1 2 3] dan dievaluasi apa adanya, sehingga literalnya boleh ditulis langsung di dalam kode. List ditulis dalam tanda kurung bulat dan berstatus kode pada pembacaan pertama, sehingga list data perlu diawali quote menjadi '(1 2 3) agar tidak dievaluasi sebagai pemanggilan. Keduanya menampung elemen dengan tipe campuran, sebagaimana satu koleksi boleh memuat angka, string, keyword, dan nil sekaligus.
; vector dievaluasi apa adanya
(prn [1 2 3])
(prn ["Ali" "Budi" "Citra"])
; list diawali quote agar dibaca sebagai data
(prn '(1 2 3))
(prn '(1 "dua" :tiga 4.0))[1 2 3]
["Ali" "Budi" "Citra"]
(1 2 3)
(1 "dua" :tiga 4.0)Perbedaan penulisan ini bukan sekadar selera tampilan. Vector adalah koleksi terindeks yang memang dirancang sebagai data, sedangkan list tanpa quote akan diproses evaluator sebagai instruksi. Quote menahan evaluasi itu sehingga isi list tetap berstatus data. Keduanya immutable, sehingga setiap operasi pengubahan mengembalikan koleksi baru dan koleksi lama tetap utuh untuk digunakan bagian lain. Jumlah elemen keduanya dihitung dengan fungsi count yang sama.
Mengakses elemen#
Akses menurut indeks dimulai dari nol. Fungsi nth mengembalikan elemen pada indeks yang diminta dan melempar error apabila indeks melewati batas, sedangkan get mengembalikan nil atau nilai bawaan yang diberikan sebagai argumen ketiga. Fungsi first dan last membaca ujung koleksi tanpa memandang tipenya, sedangkan peek membaca ujung yang paling murah diakses, yaitu akhir untuk vector dan depan untuk list.
(def angka [10 20 30 40])
; nth dan get membaca elemen menurut indeks
(println (nth angka 2))
(println (get angka 0))
(println (get angka 9))
(println (get angka 9 :kosong))
; first dan last membaca ujung koleksi
(println (first '(5 6 7)))
(println (last [5 6 7]))
; peek membaca ujung yang paling efisien
(println (peek [1 2 3]))
(println (peek '(1 2 3)))30
10
nil
:kosong
5
7
3
1Pemilihan antara nth dan get merupakan keputusan tentang maksud kode, bukan sekadar pilihan sintaks. nth menyatakan bahwa indeks di luar batas adalah situasi yang tidak seharusnya terjadi, sehingga errornya adalah sinyal adanya kekeliruan logika. Sebaliknya, get memperlakukan ketiadaan elemen sebagai kemungkinan normal pada data yang boleh tidak lengkap.
Menambah elemen dengan conj#
Fungsi conj menambah elemen pada posisi yang paling efisien menurut struktur koleksinya. Vector menambah di akhir karena penyimpanan berurutannya mendukung akses akhir yang cepat, sedangkan list berbasis tautan (linked list) menambah di depan karena penambahan kepala tidak perlu menyusuri elemen lain. Perbedaan struktur inilah yang membuat keluaran kedua contoh berikut berbeda arah meskipun pola argumennya serupa.
; conj menambah elemen di akhir vector
(prn (conj [1 2 3] 4))
(prn (conj [1 2 3] 4 5))
; conj menambah elemen di depan list
(prn (conj '(2 3 4) 1))
(prn (conj '(3 4) 1 2))[1 2 3 4]
[1 2 3 4 5]
(1 2 3 4)
(2 1 3 4)Fungsi conj menerima beberapa elemen sekaligus dan menambahkannya satu per satu pada posisi termurah koleksinya. Vector memperoleh tambahan berurutan di akhir sehingga hasilnya [1 2 3 4 5], sedangkan list memperoleh tumpukan di depan sehingga hasilnya (2 1 3 4), yaitu elemen yang ditambahkan terakhir justru menduduki kepalanya. Seluruh hasil adalah koleksi baru, karena koleksi lama tidak pernah diubah. Tipe koleksi hasil mengikuti tipe koleksi sumbernya, dan tanda kurung siku atau bulat pada keluaran memperlihatkan tipe tersebut secara langsung. Apabila urutan penambahan akhir penting bagi data, vector adalah pilihan yang tepat, sedangkan list menyediakan penambahan depan yang murah untuk pola nilai yang selalu diambil dari kepalanya.
Memilih antara vector dan list#
Aturan praktis pemilihannya sederhana, yaitu vector untuk data dan list untuk kode. Hampir semua data program ditulis sebagai vector karena akses menurut indeks berlangsung cepat, penulisannya tidak memerlukan quote, dan tanda kurung sikunya membedakan data dari kode secara visual. List menjadi pilihan alami untuk kode, baik form yang dikutip sebagai data maupun struktur yang dibangun macro sebelum dievaluasi.
| Situasi | Pilihan yang tepat |
|---|---|
| Data program, seperti daftar nilai ujian | vector |
| Akses menurut indeks yang sering dilakukan | vector |
| Form kode yang dikutip atau dibangun macro | list |
| Penambahan kepala koleksi yang berulang | list |
Fungsi count menghitung elemen pada keduanya, sebagaimana dua keluaran pertama pada contoh. Fungsi pembuat koleksi bekerja pada keduanya dan menghasilkan tipe tujuan yang eksplisit. Fungsi vector membuat vector dari argumennya, sedangkan vec mengonversi koleksi lain menjadi vector.
; count menghitung elemen pada keduanya
(println (count [1 2 3]))
(println (count '(a b c d)))
; vector dan vec membuat vector dari sumbernya
(println (vector 1 2 3))
(println (vec '(1 2 3)))3
4
[1 2 3]
[1 2 3]Pola konversi ini sering digunakan pada akhir pipeline pengolahan data agar hasil akhir kembali berbentuk vector. Penulisan yang konsisten membantu pembaca kode membedakan mana data dan mana instruksi hanya dengan melihat tanda kurungnya.
Kesalahan umum#
Menulis list data tanpa quote. Bentuk (1 2 3) tanpa quote dievaluasi sebagai pemanggilan dengan elemen pertama 1, sehingga evaluator mencoba memanggil angka sebagai fungsi dan melempar error Long cannot be cast to clojure.lang.IFn. Perbaikannya dengan mengawali quote menjadi '(1 2 3) atau mengubah penulisannya menjadi vector [1 2 3] apabila tipe yang diinginkan memang vector.
Memanggil nth dengan indeks di luar batas. Koleksi berisi tiga elemen hanya memiliki indeks 0 sampai 2, sehingga (nth angka 3) melempar IndexOutOfBoundsException. Perbaikannya dengan memeriksa count terlebih dahulu atau menggunakan get apabila ketiadaan elemen memang kemungkinan yang wajar.
Mengharapkan conj menambah di akhir list. Fungsi ini menambah di depan list karena itulah posisi termurah bagi strukturnya, sehingga hasilnya tampak berbalik arah dari kebiasaan bahasa lain. Perbaikannya dengan memilih vector apabila penambahan akhir adalah pola utama data, atau menerima urutan hasilnya sebagaimana adanya.
Ringkasan#
- Vector dan list menampung elemen heterogen yang berurutan, immutable, dan dihitung dengan count.
- Literal vector dievaluasi apa adanya, sedangkan list memerlukan quote agar berstatus data.
- nth melempar error pada indeks di luar batas, sedangkan get mengembalikan nil atau nilai bawaan.
- conj menambah elemen di akhir vector dan di depan list, mengikuti struktur internal masing-masing.
- Vector dipilih untuk data dengan akses indeks, sedangkan list dipilih untuk kode dan penambahan kepala.
Referensi#
- Data Structures — Clojure — Dokumentasi resmi vector dan list beserta operasi utamanya.
- Learn Clojure — Clojure — Panduan resmi koleksi berurutan dan akses elemennya.