Fungsi pada Clojure adalah nilai sebagaimana angka dan string, sehingga fungsi dapat menjadi argumen, hasil kembalian, sekaligus bahan penyusun fungsi baru. Topik ini membahas lima penyusun yang tersedia pada core, yaitu comp, partial, apply, complement, dan juxt, dari penjumlah sederhana hingga pipeline konversi satuan berkas dan statistik nilai ujian. Setelah topik ini dibaca, transformasi data kecil dapat dirangkai tanpa menuliskan fungsi pembungkus yang hanya digunakan satu kali.
Penyusun fungsi pada core#
Fungsi orde tinggi (higher-order function) adalah fungsi yang menerima atau mengembalikan fungsi lain. Pada data yang berulang, fungsi demikian muncul sebagai pembanding, pengubah, atau penggabung yang diteruskan ke map, filter, dan reduce. Di samping itu, Clojure menyediakan penyusun yang menghasilkan fungsi baru secara langsung, sehingga bentuk-bentuk kecil tersebut tidak perlu ditulis ulang untuk setiap kebutuhan serupa.
| Penyusun | Bentuk pemanggilan | Menghasilkan |
|---|---|---|
comp | (comp f g) | fungsi gabungan yang menerapkan g lebih dahulu, lalu f |
partial | (partial f a) | fungsi f dengan argumen pertama sudah terisi |
apply | (apply f kol) | nilai hasil f atas elemen kol sebagai argumen terpisah |
complement | (complement pred) | predikat kebalikan dari pred |
juxt | (juxt f g) | fungsi yang mengembalikan vector [hasil-f hasil-g] |
Kelima entri di atas terbagi menjadi dua kelompok. comp, partial, complement, dan juxt mengembalikan fungsi, sedangkan apply segera mengembalikan nilai karena seluruh argumennya sudah tersedia pada pemanggilan itu juga. Pembeda ini menentukan penempatan kurung, yaitu fungsi hasil penyusun dipanggil dengan argumen sisa, sedangkan apply langsung menghasilkan nilai. Bagian berikut membahas setiap penyusun dengan contoh yang dapat dieksekusi, mulai dari komposisi, konversi satuan, statistik nilai ujian, hingga penyusunan laporan.
comp dan urutan kanan ke kiri#
comp menggabungkan beberapa fungsi menjadi satu. Fungsi hasilnya menerapkan argumen paling kanan lebih dahulu, kemudian meneruskan hasilnya ke kiri, sebagaimana notasi komposisi . comp tanpa argumen menghasilkan identity, dan satu argumen menghasilkan fungsi itu sendiri.
(def dobel #(* % 2))
(def tambah-satu inc)
(def dobel-lalu-tambah (comp tambah-satu dobel))
(def tambah-lalu-dobel (comp dobel tambah-satu))
(println "(dobel-lalu-tambah 5) =" (dobel-lalu-tambah 5))
(println "(tambah-lalu-dobel 5) =" (tambah-lalu-dobel 5))(dobel-lalu-tambah 5) = 11
(tambah-lalu-dobel 5) = 12Pada dobel-lalu-tambah, fungsi dobel berada paling kanan sehingga dievaluasi lebih dahulu, lalu tambah-satu meneruskan hasilnya menjadi 11. Urutan penulisan comp memang kebalikan urutan eksekusinya, sehingga penamaan yang menyebut urutan kerja, seperti dobel-lalu-tambah, membantu pembaca mengurai pipeline tanpa membaca ulang ekspansinya.
partial dan pipeline konversi satuan#
partial mengisi sebagian argumen sebuah fungsi dan mengembalikan fungsi baru yang menunggu argumen sisanya. Bentuk ini menghilangkan banyak fungsi anonim satu baris, karena (partial * 0.0009765625) lebih ringkas daripada #(* % 0.0009765625) dan terbaca sebagai “kalikan 0.0009765625”. Pipeline konversi satuan berikut mengubah ukuran berkas dari byte. Nilai 0.0009765625 sama persis dengan 1/1024, sehingga hasil perkaliannya bersih tanpa pembulatan.
(def byte-ke-kb (partial * 0.0009765625))
(def byte-ke-mb (comp byte-ke-kb byte-ke-kb))
(def byte-ke-gb (comp byte-ke-kb byte-ke-kb byte-ke-kb))
(println "1536 byte =" (byte-ke-kb 1536) "KB")
(println "5242880 byte =" (byte-ke-mb 5242880) "MB")
(println "1073741824 byte=" (byte-ke-gb 1073741824) "GB")1536 byte = 1.5 KB
5242880 byte = 5.0 MB
1073741824 byte= 1.0 GBFungsi byte-ke-mb adalah komposisi konversi yang sama dua kali, karena satu kali konversi membawa byte ke kilobyte dan konversi berikutnya membawa kilobyte ke megabyte. Begitu pula byte-ke-gb menumpuk konversi yang sama tiga kali. Dengan comp, ketiga lapisan konversi menjadi satu fungsi tanpa nama perantara, sementara setiap lapisan tetap dapat diuji sendiri-sendiri.
apply dan statistik nilai ujian#
apply membuka sebuah koleksi menjadi argumen posisi. Pemanggilan (apply max nilai-uji) ekuivalen dengan (max 80 85 78 92 88) ketika nilai-uji berisi kelima angka tersebut. Fungsi aritmetika dan pembanding pada core menerima angka sebagai argumen terpisah, sehingga koleksi perlu dibuka terlebih dahulu sebelum diteruskan.
(def nilai-uji [80 85 78 92 88])
(println "Maksimum :" (apply max nilai-uji))
(println "Minimum :" (apply min nilai-uji))
(println "Total :" (apply + nilai-uji))
(println "Rerata :" (double (/ (apply + nilai-uji) (count nilai-uji))))
(def total-dengan-ongkir (partial + 15000))
(println "Subtotal 60000 + ongkir:" (total-dengan-ongkir 60000))Maksimum : 92
Minimum : 78
Total : 423
Rerata : 84.6
Subtotal 60000 + ongkir: 75000Pembeda antara partial dan apply terletak pada hasilnya. partial mengembalikan fungsi yang menunggu argumen berikutnya, sedangkan apply segera mengembalikan nilai. Pembagian total oleh count menghasilkan pecahan eksak, dan double mengubahnya menjadi angka desimal biasa agar terbaca sebagai 84.6. Baris terakhir menunjukkan partial yang menetapkan ongkir sebagai tetapan, sehingga fungsi total-dengan-ongkir hanya menunggu nilai subtotal.
complement dan juxt#
complement membalik sebuah predikat tanpa menulis ulang logikanya. Apabila #(>= % 80) menyatakan syarat nilai minimal 80, (complement #(>= % 80)) menyatakan kebalikannya dengan satu definisi kebenaran yang sama, sehingga kedua arah filter tidak mungkin berbeda makna. juxt menjalankan beberapa fungsi terhadap satu input dan mengumpulkan hasilnya dalam satu vector. Bentuk ini menghindari pemanggilan berulang dan menyatukan beberapa laporan atas satu sumber data.
(def nilai-uji [80 85 78 92 88])
(println "Memenuhi syarat :" (filter #(>= % 80) nilai-uji))
(println "Tidak memenuhi :" (filter (complement #(>= % 80)) nilai-uji))
(println "Ekstrem :" ((juxt #(apply min %) #(apply max %)) nilai-uji))
(println "Laporan nilai :" ((juxt :nama :nilai) {:nama "Budi" :nilai 88}))Memenuhi syarat : (80 85 92 88)
Tidak memenuhi : (78)
Ekstrem : [78 92]
Laporan nilai : [Budi 88]filter menghasilkan seq sehingga keluarannya dicetak berbentuk list, sedangkan juxt selalu mengembalikan vector. Pada baris Ekstrem, apply membuka koleksi untuk setiap anggota juxt karena min dan max mengharapkan angka terpisah. Baris terakhir menunjukkan juxt atas keyword, yang membaca dua kunci dari map nilai sekaligus.
Kesalahan umum#
Kesalahan pertama berupa urutan comp yang terbalik. Karena evaluasi bergerak dari kanan ke kiri, (comp inc #(* % 2)) dan (comp #(* % 2) inc) menghasilkan angka yang berbeda, dan urutan yang benar diperiksa dengan satu nilai uji sederhana sebelum dipasangkan ke data nyata. Kesalahan kedua berupa partial pada fungsi non-komutatif.
Kesalahan ketiga berupa juxt yang menerima min dan max secara langsung atas koleksi. Kedua fungsi itu mengharapkan angka terpisah, dan dengan satu argumen koleksi keduanya mengembalikan koleksi itu sendiri, sehingga hasilnya bukan statistik yang dimaksud. Setiap anggotanya perlu dibungkus apply, sebagaimana contoh pada bagian complement dan juxt. Kesalahan terakhir berupa anggapan bahwa partial mengembalikan nilai, padahal hasilnya selalu berupa fungsi yang menunggu pemanggilan dengan argumen sisanya.
Ringkasan#
compmenggabungkan fungsi menjadi satu dan mengevaluasi dari kanan ke kiri, sebagaimana notasi .partialmenetapkan argumen terdepan dan mengembalikan fungsi yang menunggu argumen sisanya.applymembuka koleksi menjadi argumen posisi bagi fungsi variadik sepertimax,min, dan+.complementmembalik predikat, sedangkanjuxtmengumpulkan hasil beberapa fungsi atas satu input ke dalam satu vector.
Referensi#
- Functional Programming — Clojure.org — Referensi resmi pemrograman fungsional Clojure, termasuk fungsi orde tinggi dan komposisi.
- comp — ClojureDocs
— Dokumentasi
compdengan contoh komposisi beberapa fungsi. - partial — ClojureDocs
— Dokumentasi
partialbeserta perbandingannya dengan fungsi anonim. - apply — ClojureDocs
— Dokumentasi
applyatas koleksi sebagai argumen posisi.