Abstraksi seq adalah antarmuka sekuensial yang membuat sekumpulan fungsi, yaitu first, rest, dan cons, dapat bekerja atas vector, list, string, map, set, maupun hasil generator tanpa menyesuaikan kode dengan tipe sumbernya. Topik ini membahas konsep seqable, perilaku seq atas berbagai sumber, perbedaan rest dan next, serta pola penulisan fungsi yang tidak bergantung pada tipe koleksi.
Seq sebagai pandangan sekuensial#
Seq bukan tipe data tambahan yang menyimpan elemen sendiri, melainkan pandangan logis (view) atas sumber sekuensial. Fungsi seq menerima nilai apa pun yang seqable, yaitu yang dapat dipandang sebagai barisan elemen, kemudian mengembalikan pandangan tersebut. Istilah seqable menunjuk pada sekumpulan tipe yang memenuhi syarat itu, dan hampir seluruh koleksi inti Clojure termasuk di dalamnya. Apabila sumber kosong atau bernilai nil, seq mengembalikan nil, sehingga pemanggilan (seq data) juga menjadi idiom untuk menguji ada tidaknya elemen.
Panggilan seq atas berbagai sumber menghasilkan pandangan berikut.
(prn (seq [13 42 7]))
(prn (seq "abc"))
(prn (seq {:a 1}))
(prn (seq #{:x :y}))
(prn (seq nil))(13 42 7)
(\a \b \c)
([:a 1])
(:y :x)
nilKeluaran di atas memperlihatkan lima perilaku sekaligus. Seq atas vector tampil berbentuk list, sehingga bentuk cetak tidak menggambarkan tipe asalnya. Seq atas string "abc" berupa barisan karakter, bukan string utuh. Seq atas map berupa pasangan entri berbentuk vector dua elemen, sedangkan seq atas set mengikuti urutan struktur internal set dan bukan urutan penyisipan, terlihat dari hasil (:y :x). Terakhir, seq atas nil mengembalikan nil tanpa melempar exception.
Pandangan seq menjadi dasar bagi fungsi sekuens lain di pustaka inti, seperti second, take, dan drop, karena semuanya tersusun atas kombinasi first dan rest yang sama. Memahami first, rest, dan cons berarti memahami cara kerja seluruh lapisan di atasnya.
first, rest, dan next#
first mengembalikan elemen pertama dari seq, sedangkan rest mengembalikan seq tanpa elemen pertamanya. Keduanya menerima apa pun yang seqable karena menjalankan seq terhadap argumennya terlebih dahulu. Kode berikut menelusuri vector dan menunjukkan perilaku keduanya sampai ke ujung seq.
(def nilai [10 20 30 40])
(println (first nilai))
(prn (rest nilai))
(prn (next nilai))
(prn (rest [42]))
(prn (next [42]))
(println (first nil))
(prn (rest nil))
(prn (next nil))10
(20 30 40)
(20 30 40)
()
nil
nil
()
nilTiga perilaku di ujung seq patut diperhatikan. Pertama, rest atas [42] mengembalikan seq kosong yang dicetak (), bukan nil. Kedua, next mengembalikan nil pada posisi yang sama, sehingga next lebih mudah digunakan sebagai kondisi penghentian perulangan. Ketiga, first atas nil mengembalikan nil dan rest atas nil mengembalikan seq kosong, keduanya tanpa exception. Aturan ini membuat penelusuran seq dapat ditulis tanpa pemeriksaan tipe tambahan.
Kombinasi first dan next adalah pola dasar penelusuran manual. first mengambil elemen pada satu posisi, sedangkan next maju satu langkah, sehingga perulangan yang menguji next berhenti tepat di ujung seq tanpa kondisi tambahan. cons melengkapi tiga fungsi dasar tersebut dengan menyambungkan satu elemen di depan seq.
(prn (cons 0 [1 2 3]))
(prn (cons :a (seq {:b 2})))
(prn (cons \c "at"))(0 1 2 3)
(:a [:b 2])
(\c \a \t)cons tidak mengubah sumbernya. Hasilnya adalah seq baru yang elemen pertamanya berasal dari argumen pertama cons, sedangkan sisanya adalah pandangan atas sumber semula. Pada seq map, elemen entri berbentuk vector dua elemen, sehingga (cons :a (seq {:b 2})) tampil sebagai (:a [:b 2]). Konsistensi bentuk inilah yang membuat cons, first, dan rest dapat disusun menjadi fungsi penelusuran apa pun.
Satu kode untuk semua sumber#
Karena first, rest, dan cons bekerja seragam, fungsi yang hanya menggunakan ketiganya otomatis berlaku untuk semua sumber sekuensial. Fungsi dua-pertama berikut mengambil dua elemen awal tanpa mengetahui tipe sumbernya.
(defn dua-pertama [s]
(list (first s) (second s)))
(prn (dua-pertama [10 20 30]))
(prn (dua-pertama "belajar"))
(prn (dua-pertama {:nama "Ali" :umur 20}))
(prn (dua-pertama (range 100)))(10 20)
(\b \e)
([:nama "Ali"] [:umur 20])
(0 1)Satu definisi yang sama melayani vector, string, map, maupun seq dari range. Pada string, elemen yang diambil berupa karakter \b dan \e. Pada map, elemen yang diambil berupa entri [:nama "Ali"]. Pada (range 100), fungsi ini hanya membaca dua elemen pertama, sehingga sumber yang jauh lebih besar tidak menjadi beban bagi dua-pertama.
Pola inilah yang membuat fungsi sekuens di pustaka inti, termasuk map dan filter, dapat digunakan atas koleksi apa pun tanpa perulangan yang ditulis sendiri. Fungsi generik semacam ini juga mudah diuji karena perilakunya tidak berubah ketika sumber diganti, cukup dengan mengganti argumen pada pemanggilan yang sama. Satu kasus uji dengan vector kecil sudah mencakup perilaku yang kelak berlaku pula pada sumber yang jauh lebih besar.
Kesalahan umum#
- Mengandalkan
restsebagai kondisi penghentian. Karenarestmengembalikan seq kosong alih-alih nil, perulangan yang menguji(if s ...)tidak pernah berhenti di ujung seq. Perbaikannya, gunakannextatau bungkus denganseq, misalnya(if (seq s) ...). - Menganggap hasil
restbertipe sama dengan sumbernya.restatas vector mengembalikan seq, bukan vector, sehingga operasi khusus vector tidak berlaku langsung pada hasilnya. Apabila tipe hasil penting, kumpulkan kembali secara eksplisit dengan(into [] (rest nilai)). - Menunggu exception pada koleksi kosong.
firstdanrestatas nil atau koleksi kosong tidak melempar exception, sehingga kegagalan akibat data kosong baru terlihat di tengah alur. Pemeriksaan(seq data)di awal membuat kekosongan tersebut eksplisit.
Ringkasan#
- Abstraksi seq memandang sumber apa pun yang seqable sebagai barisan elemen, dan
seqmengembalikan nil untuk sumber kosong. first,rest, dannextbekerja seragam atas vector, string, map, set, maupun seq.restmengembalikan seq kosong di ujung, sedangkannextmengembalikan nil.consmenyambung satu elemen di depan seq tanpa mengubah sumbernya.- Fungsi yang hanya menggunakan
first,rest, danconsberlaku untuk semua sumber sekuensial.
Referensi#
- Sequences — Clojure
— dokumentasi resmi abstraksi seq beserta
seq,first,rest, dancons. - Data Structures — Clojure — dokumentasi koleksi inti Clojure beserta hubungan tiap koleksi dengan seq.