Lazy sequence menunda perhitungan elemen sampai elemen itu benar-benar diminta, sehingga seq tak terbatas dapat dideklarasikan lalu dibatasi dengan take. Topik ini membahas mekanisme realisasi saat diminta, generator inti range, repeat, dan iterate, serta fungsi pemaksa realisasi doall dan dorun beserta perbedaan keduanya.
Realisasi saat diminta#
Sebuah lazy sequence menyimpan resep perhitungan, bukan hasilnya. Elemen baru dihitung pada saat dibaca pertama kali, kemudian hasilnya disimpan agar pembacaan berikutnya tidak menghitung ulang. Untuk memperlihatkan momen realisasi secara terukur, kode berikut menghitung berapa kali fungsi transformasi dipanggil. Penghitungnya menggunakan atom, yaitu referensi tunggal yang isinya dapat diperbarui secara aman, dan di sini hanya berperan sebagai alat ukur.
(def panggilan (atom 0))
(def s (map (fn [x] (swap! panggilan inc) (* x x)) (iterate inc 1)))
(println "belum direalisasikan:" @panggilan)
(prn (take 3 s))
(println "setelah take 3:" @panggilan)
(prn (take 5 s))
(println "setelah take 5:" @panggilan)belum direalisasikan: 0
(1 4 9)
setelah take 3: 3
(1 4 9 16 25)
setelah take 5: 5Sebelum ada pembacaan, penghitung bernilai 0, yang berarti tidak ada satu pun elemen yang dihitung. Perintah (take 3 s) memicu tepat tiga pemanggilan transformasi dan menghasilkan (1 4 9). Perintah (take 5 s) kemudian hanya menambah dua pemanggilan karena tiga elemen pertama sudah tersimpan. Realisasi terjadi tepat sebagaimana diminta, dan elemen yang sudah dihitung tidak dihitung ulang.
Sumber pada contoh sengaja menggunakan iterate, yang elemennya direalisasikan satu per satu. Sumber seperti range dan vector berperilaku berbeda karena realisasinya per 32 elemen, dan perbedaan itu dibahas pada topik Jebakan Laziness.
Laziness juga membuat komposisi pipeline tetap murah. map dan filter yang berantai tidak membangun koleksi antara, karena setiap elemen dihitung pada saat konsumen membacanya. Fungsi transformasi pada contoh mengembalikan kuadrat setelah mencatat panggilannya, sehingga isi seq tetap utuh dan penghitung hanya berperan sebagai alat ukur.
Generator sekuens inti#
Pustaka inti menyediakan generator sekuens yang seluruhnya berupa lazy sequence. range menghasilkan barisan angka, repeat mengulang satu nilai, dan iterate menerapkan sebuah fungsi berulang kali terhadap hasil sebelumnya.
(prn (range 0 100 10))
(prn (take 5 (range)))
(prn (repeat 3 "x"))
(prn (take 4 (repeat 7)))
(prn (take 5 (iterate inc 1)))
(prn (take 6 (iterate #(* 2 %) 1)))(0 10 20 30 40 50 60 70 80 90)
(0 1 2 3 4)
("x" "x" "x")
(7 7 7 7)
(1 2 3 4 5)
(1 2 4 8 16 32)range memiliki empat bentuk pemanggilan. Satu argumen menghitung dari nol, dua argumen menentukan rentang, tiga argumen menambah langkah, seperti (range 0 100 10) yang menghasilkan kelipatan sepuluh, dan tanpa argumen menghasilkan barisan tak terbatas. repeat dengan satu argumen mengulang satu nilai tanpa batas, sedangkan dengan dua argumen mengulang sebanyak hitungan yang diberikan. iterate dengan inc menaikkan nilai satu per satu, sedangkan #(* 2 %) melipatgandakan nilai sehingga menghasilkan barisan pangkat dua.
Tiga generator ini menggantikan pola perulangan pengisi koleksi yang lazim pada bahasa lain. Barisan angka dinyatakan sebagai range, nilai tetap yang berulang dinyatakan sebagai repeat, dan barisan yang aturannya berulang dinyatakan sebagai iterate. Pembatasan dengan take membuat seq tak terbatas aman digunakan. Ekspresi (take 5 (range)) hanya menghitung lima angka dan tidak pernah menyentuh sisanya.
Memaksa realisasi dengan doall dan dorun#
Realisasi kadang perlu dipaksa sebelum hasilnya digunakan, misalnya untuk memastikan efek samping sudah berjalan atau untuk memisahkan biaya perhitungan dari penggunaan hasilnya. Fungsi realized? memeriksa apakah sebuah lazy sequence sudah direalisasikan.
(def s (map inc (range 5)))
(println (realized? s))
(dorun s)
(println (realized? s))false
trueEkspresi (map inc (range 5)) menghasilkan seq yang belum menyimpan apa pun, sehingga realized? mengembalikan false. Setelah dorun menjalankan seluruh realisasi, realized? mengembalikan true. Fungsi realized? berguna pada pengujian dan pemrofilan karena status realisasi tidak terlihat dari luar. Terdapat dua fungsi pemaksa realisasi yang perlu dibedakan, yaitu doall dan dorun.
(def hasil-doall (doall (map inc (range 5))))
(prn hasil-doall)
(def hasil-dorun (dorun (map inc (range 5))))
(prn hasil-dorun)(1 2 3 4 5)
nildoall merealisasikan seluruh elemen lalu mengembalikan seq-nya, sedangkan dorun merealisasikan seluruh elemen lalu mengembalikan nil. doall digunakan ketika hasilnya masih diperlukan, misalnya untuk pencetakan atau pengujian, dengan konsekuensi rujukan ke seluruh seq tetap tertahan. dorun digunakan ketika hanya efek sampingnya yang penting, karena nil yang dikembalikannya melepaskan rujukan ke rantai elemen. Pada seq besar, perbedaan ini berdampak langsung pada penggunaan memori.
Pemaksaan juga berguna ketika biaya perhitungan ingin dipisahkan dari penggunaan hasil. Realisasi dipicu sekali di depan melalui doall, sehingga pengukuran atau pencatatan pada tahap berikutnya tidak lagi mencakup perhitungan yang tertunda.
Kesalahan umum#
- Mencetak seq tak terbatas secara langsung. Ekspresi
(println (range))tidak pernah selesai karena pencetakan menuntut elemen tanpa akhir. Batasi dengantakesebelum dicetak atau sebelum dikonsumsi fungsi apa pun. - Menganggap
takemengembalikan vector. Hasiltaketetap berupa lazy sequence, terlihat dari tampilan cetaknya yang berbentuk list. Kumpulkan dengan(into [] ...)bila vector memang diperlukan. - Melewatkan perbedaan aritas
repeat.(repeat "x")mengulang tanpa batas, sedangkan(repeat 3 "x")mengulang tiga kali. Kesalahan aritas pada fungsi ini baru tampak saat seq tak terbatasnya dikonsumsi fungsi rakus. - Mengira lazy sequence menyimpan hasil sejak awal. Perhitungan baru terjadi saat pembacaan, sehingga pencatatan yang dilakukan sebelum konsumsi tidak menangkap biaya perhitungannya. Paksa realisasi dengan
doallataudorunterlebih dahulu bila pencatatannya harus lengkap.
Ringkasan#
- Lazy sequence menunda perhitungan hingga elemen dibaca, kemudian menyimpan hasilnya agar tidak dihitung ulang.
range,repeat, daniteratemenghasilkan seq, termasuk yang tak terbatas, dan pembatasannya dilakukan lewattake.realized?memeriksa status realisasi,doallmengembalikan seq yang sudah direalisasikan, sedangkandorunmengembalikan nil dan melepaskan rujukannya.
Referensi#
- clojure.core/range — ClojureDocs — dokumentasi range beserta keempat bentuk pemanggilannya.
- clojure.core/iterate — ClojureDocs — dokumentasi iterate beserta contoh seq tak terbatas.
- Sequences — Clojure — dokumentasi resmi laziness dan fungsi-fungsi pemaksa realisasi.