Komposisi Fungsi: Merakit Fungsi Kecil
Menggabungkan fungsi-fungsi kecil dengan compose dan pipe, plus currying: cara fungsional membangun alur data yang jelas dan mudah diuji.
Kenapa Komposisi Ada
Program nyata jarang selesai dalam satu langkah. Data mentah perlu dibersihkan, diformat, lalu ditampilkan. Cara umum: menulis satu fungsi raksasa yang melakukan semuanya, atau menumpuk pemanggilan seperti tampilkan(format(bersihkan(data))) yang dibaca dari dalam ke luar dan cepat membingungkan. Komposisi fungsi menawarkan jalan ketiga: rakit fungsi-fungsi kecil yang masing-masing melakukan satu hal, lalu sambungkan menjadi alur data yang terbaca seperti kalimat.
Compose dan Pipe
Compose menjalankan fungsi dari kanan ke kiri, pipe dari kiri ke kanan. Pipe biasanya lebih enak dibaca karena urutannya sama dengan urutan eksekusi.
const bersihkan = (s) => s.trim().toLowerCase();
const kapital = (s) => s.charAt(0).toUpperCase() + s.slice(1);
const tambahTitik = (s) => s + ".";
// tanpa komposisi: dibaca dari dalam ke luar
console.log(tambahTitik(kapital(bersihkan(" halo dunia "))));
// pipe: dibaca kiri ke kanan, seperti alur
const pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), x);
const rapikan = pipe(bersihkan, kapital, tambahTitik);
console.log(rapikan(" halo dunia ")); // "Halo dunia."Versi compose tinggal membalik urutannya:
const compose = (...fns) => (x) => fns.reduceRight((v, f) => f(v), x);
const rapikan2 = compose(tambahTitik, kapital, bersihkan);
console.log(rapikan2(" halo dunia ")); // "Halo dunia."Keuntungannya: tiap fungsi kecil bisa diuji sendiri-sendiri, dan alur rapikan bisa dipakai ulang di mana saja tanpa mengulang logika.
Currying: Fungsi yang Menunggu
Currying mengubah fungsi multi-argumen menjadi rantai fungsi satu argumen. Tujuannya: membuat versi "spesialis" dari fungsi umum.
const tambah = (a) => (b) => a + b;
const tambahLima = tambah(5);
console.log(tambahLima(10)); // 15
// contoh nyata: pembuat pesan
const sapa = (sapaan) => (nama) => \`\${sapaan}, \${nama}!\`;
const sapaPagi = sapa("Selamat pagi");
console.log(sapaPagi("Rina")); // "Selamat pagi, Rina!"Currying bersinar saat dipakai dengan komposisi: kamu bisa menyiapkan fungsi yang sudah "diisi sebagian" lalu menyambungkannya ke pipe.
Contoh Nyata: Alur Data Belanja
const keRupiah = (n) => "Rp" + n.toLocaleString("id-ID");
const tambahPPN = (n) => Math.round(n * 1.11);
const bulatkanRibu = (n) => Math.round(n / 1000) * 1000;
const hargaFinal = pipe(tambahPPN, bulatkanRibu, keRupiah);
console.log(hargaFinal(50000)); // "Rp56.000"Logika harga terpisah rapi: PPN, pembulatan, dan format masing-masing satu fungsi. Kalau aturan PPN berubah, kamu hanya menyentuh satu tempat.
Jebakan Umum
Pertama, komposisi hanya nyaman untuk fungsi murni (tanpa efek samping dan hasilnya hanya bergantung pada input). Kalau fungsinya mengubah variabel global, alur pipe jadi sulit diprediksi. Kedua, jangan berlebihan: merakit 15 fungsi satu-baris untuk tugas sederhana justru menambah beban baca. Ketiga, urutan itu segalanya: pipe(a, b) berbeda dengan pipe(b, a), jadi beri nama alur dengan jelas.
Catatan teknis: Pola ini adalah fondasi pemrograman fungsional dan dipakai pustaka nyata: Redux memakai compose untuk middleware, dan Lodash/Ramda menyediakan
flowdancurrysiap pakai. Memahami konsepnya membuat dokumentasi pustaka tersebut langsung masuk akal.
export default function App(): JSX.Element { return <h1>Hello world</h1> }
Tantangan
Alur Format Harga
Di toko online nyata, harga mentah dari database harus melewati beberapa transformasi sebelum tampil ke pembeli: diskon, pajak, pembulatan, format mata uang. Kalau ditulis sebagai satu fungsi raksasa, perubahan satu aturan berisiko merusak yang lain. Buat fungsi pipe lalu rakit alur: kurangi diskon 10 persen, tambah PPN 11 persen, bulatkan ke ribuan, format ke Rupiah.
<!doctype html> <html> <head> <meta charset="utf-8" /> </head> <body> <h1>Halo JS</h1> <p>Buka console preview untuk melihat output.</p> <script src="index.js"></script> </body> </html>