Cleaner Code dengan Function Composition

Eksplorasi fungsi compose dengan ES6 untuk code yang lebih rapi

Published in
3 min readMar 26, 2017

--

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
Berasa ngoding Lisp / Clojure (no offence) :p

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! 😁

--

--

Software Developer @Chordify, Utrecht. NOTE: Please navigate to https://jihadwaspada.com. I no longer write on Medium