Cleaner Code dengan Function Composition
Eksplorasi fungsi compose dengan ES6 untuk code yang lebih rapi
Katanya sih, Javascript support functional programming, atau setidaknya functional programming style. Dimana pada dasarnya, konsep software craftmanship pada functional programming adalah Composition over Inheritance, alias bahasa gampangnya kita hanya menggabungkan fungsi-fungsi yang kita buat menjadi fungsi lain yang lebih besar. Dan salah satu cara untuk memfasilitasi itu adalah dengan membuat fungsi sederhana yang akan kita sebut compose()
.
Apa itu compose?
Jika kita memiliki dua buah fungsi: const add2 = x => x + 2
dan const times3 = x * 3
, biasanya untuk menggabungkan kedua fungsi ini adalah dengan cara memanggilnya langsung:
const add2ThenTimes3 = n => times3(add2(n))console.log(add2ThenTimes3(1))
// => 9
Gak ada salahnya sih, cuman kalo misalnya fungsi kita ternyata banyak memanggil fungsi-fungsi lain di dalamnya, bisa-bisa sisi readability code kita menjadi hal yang harus dikorbankan. Bertambahnya pemanggilan fungsi berbanding lurus dengan jumlah kurung buka + kurung tutup dibagi 2 😁.
const diskon10 = x => x - (x * 10/100)
const hargaKambing = harga => `Jual kambing harga $${harga}`
const wedhus = n => hargaKambing(diskon10(times3(add2(n))))// dan seterusnya
Nah disini lah kita bisa mulai mempertimbangkan kehadiran fungsi compose
sebagai glue terhadap fungsi-fungsi yang kita panggil. Dimana:
const wedhusA = n => hargaKambing(diskon10(times3(add2(n))))// sama persis denganconst wedhusB = compose(hargaKambing, diskon10, times3, add2)
console.log(wedhusA(1))
// => "Jual kambing harga $8.1"
console.log(wedhusB(1))
// => "Jual kambing harga $8.1"
Untuk pembacaan sama aja kok, dari kanan ke kiri. Tapi dari penampakan sih harusnya bisa lebih lega karena beberapa parenthesis atau tanda kurung baru saja kita kurangi.
compose(f1,f2,f3,f4,…)(x) == f1(f2(f3(f4(…(x)))))
Membuat compose
Fungsi compose
bisa dibuat dengan menggunakan reduceRight
yang disediakan Javascript. reduceRight
sama dengan reduce
, bedanya ya perhitungan iterasinya dimulai dari elemen yang paling akhir.
Referensi reduce dan reduceRight. Wajib dibaca bagi yang belum paham fungsi reduce pada Javascript
compose
dalam ES6 sederhananya dapat ditulis seperti ini:
Hmm, agak bingung? Mari kita breakdown step by step supaya lebih jelas. Pertama-tama, kita harus pahami dulu bahwa:
Kemudian proses reduction-nya dapat dicerna dengan mudah:
Sederhana bukan?
Best practice
IMHO, agar fungsi-fungsi yang kita buat dapat di-compose dengan mudah, syarat pertama adalah dengan membuat fungsi unary, atau fungsi yang memiliki 1 argument saja. Misal:
const reverse = str => str.split('').reverse().join('')console.log(reverse('ganteng'))
// => gnetnag
Hmm, bagaimana kalau saya butuh 2 argument? Tinggal di-curry-kan saja, seperti fungsi map
di bawah:
const map = fn => list => list.map(fn)
const bolakBalik = compose(
map(reverse),
reverse
)console.log(bolakBalik(['ganteng', 'tampan', 'kiyut']))
// => ['tuyik', 'napmat', 'gnetnag']
Kalau argument-nya 3 atau lebih gimana? Sama, tinggal di-curry-kan saja 😅. Contohnya adalah pada fungsi reduce dibawah ini:
const reduce = (fn, init) => list => list.reduce(fn, init)
const gabung = compose(
reduce((val, acc) => `${acc} - ${val}`, ''),
bolakBalik // compose bisa dimasukin dalem compose juga lho
)console.log(gabung(['ganteng', 'tampan', 'kiyut']))
// => 'tuyik - napmat - gnetnag'
Syarat yang kedua adalah jika fungsi yang dibuat memiliki lebih dari satu argument, tempatkan data yang ingin diolah sebagai parameter function kembaliannya (karena currying tadi). Contohnya fungsi map
dan reduce
di atas: saya menempatkan variable list
sebagai argument fungsi hasil currying. So, jika fungsimu memiliki lebih dari satu argument: operations/requirements come first, data come last.
Kesimpulan
Dengan compose
, kita dapat meningkatkan code kita menjadi lebih readable, mengurangi distraksi parenthesis akibat pemanggilan fungsi yang semakin panjang. Dengan compose
juga, kita baru saja membuat fungsi yang bersifat point-free function, yaitu fungsi yang tidak memerlukan penulisan argument yang akan di-apply (lihat fungsi wedhus
, gabung
dan bolakBalik
di atas). Fungsi hasil dari compose
pun dapat di-compose juga, sehingga secara tidak langsung mendorong kita untuk membuat fungsi-fungsi dasar yang reusable, which is good.
compose(f1, f2, f3) == compose(compose(f1, f2), f3) == compose(f1, compose(f2, f3))
Final Note
Belakangan, saya jadi sering menggunakan Ramda untuk coding sehari-hari, karena nature-nya yang otomatis currying dan support coding style àla functional programming. Ngoding bisa jadi lebih enjoy hehe.
Semoga tulisan ini bermanfaat bagi kawan-kawan semua. Happy coding! 😁