Abstraksi Seq

Memahami abstraksi seq sebagai dasar kerja first dan rest atas sumber sekuensial apa pun.

Daftar isi

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.

clojure
(prn (seq [13 42 7]))
(prn (seq "abc"))
(prn (seq {:a 1}))
(prn (seq #{:x :y}))
(prn (seq nil))
text Keluaran
(13 42 7)
(\a \b \c)
([:a 1])
(:y :x)
nil

Keluaran 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.

clojure
(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))
text Keluaran
10
(20 30 40)
(20 30 40)
()
nil
nil
()
nil

Tiga 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.

clojure
(prn (cons 0 [1 2 3]))
(prn (cons :a (seq {:b 2})))
(prn (cons \c "at"))
text Keluaran
(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.

clojure
(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)))
text Keluaran
(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 rest sebagai kondisi penghentian. Karena rest mengembalikan seq kosong alih-alih nil, perulangan yang menguji (if s ...) tidak pernah berhenti di ujung seq. Perbaikannya, gunakan next atau bungkus dengan seq, misalnya (if (seq s) ...).
  • Menganggap hasil rest bertipe sama dengan sumbernya. rest atas 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. first dan rest atas 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 seq mengembalikan nil untuk sumber kosong.
  • first, rest, dan next bekerja seragam atas vector, string, map, set, maupun seq. rest mengembalikan seq kosong di ujung, sedangkan next mengembalikan nil.
  • cons menyambung satu elemen di depan seq tanpa mengubah sumbernya.
  • Fungsi yang hanya menggunakan first, rest, dan cons berlaku untuk semua sumber sekuensial.

Referensi#