Applicative (source)

Functional Programming: Mengenal Applicative Functor

dalam ES6

Published in
6 min readJun 24, 2017

--

Kembali lagi kita bahas satu per satu konsep Functional Programming dalam JavaScript. Kali ini kita akan membahas Applicative Functor. Namun, untuk memahami konsep yang sebenernya sederhana ini (yang nggak semenyeramkan namanya), ada baiknya untuk terlebih dahulu paham apa itu Functor dan bagaimana cara kerjanya. Karena sejatinya, Applicative ini adalah subclass/turunan dari kategori Functor. Bagi yang ingin berkenalan dengan Functor atau sekedar mengingat-ingat kembali, bisa baca artikel saya disini:

Sekedar tambahan, di artikel ini juga ada sedikit pembahasan soal Monad. Maka saya rekomendasikan untuk setidaknya get the idea what Monad is:

Kasus Dasar

Langsung aja ke contoh kasus ya. Kita semua tahu bahwa Maybe merupakan sebuah Functor. Dan salah satu cara untuk memodifikasi nilai dari suatu Functor adalah dengan memanggil fungsi map-nya. Sebagai contoh:

const add = (x, y) => x + y
const incr = a => add(1, a)
const maybeOne = Maybe(1)
const maybeTwo = maybeOne.map(incr) // => Maybe(2)

Bagaimana kalau kita coba lakukan operasi penambahan pada dua buah Functor?

const maybe1 = Maybe(1)
const maybe2 = Maybe(2)
const maybe3 = maybe1.bind(satu =>
maybe2.map(dua => add(satu, dua))
)
// Maybe(3)

Bagaimana jika menambahkan 3 functor?

const maybe1 = Maybe(1)
const maybe2 = Maybe(2)
const maybe3 = Maybe(3)
const maybe6 = maybe1
.bind(satu => maybe2
.bind(dua => maybe3
.map(tiga => add(satu, dua, tiga))
))
// Maybe(6)

So far so good. Namun kalau ditinjau lebih teliti, persamaan dari kedua solusi di atas adalah kita harus “membuka” masing-masing Functor secara manual (dengan memanggil .bind dan .map) agar nilainya dapat dijumlahkan dengan nilai Functor sebelumnya. Dan kemudian di Functor terakhir, memanggil function dan mengisi argument-nya dengan nilai-nilai yang sudah dibuka secara manual juga.

Adakah solusi yang lebih baik?

Penasaran dengan method .bind? Kamu bisa baca artikelnya disini

Sekilas Tentang Currying

Curried function adalah fungsi yang jika dipanggil namun belum lengkap argument-nya, maka akan mengembalikan fungsi baru dengan argument yang tersisa. Ada 2 cara untuk membuat curried function:

2 cara function currying

Manfaat currying akan terlihat sebentar lagi.

Now Introduce the Applicative Functor…

Kata kunci pertama agar mudah diingat:

Applicative Functor itu anggap saja sebagai function yang dibungkus oleh Functor

Jika method khas àla Functor adalah map, dan Monad adalah bind/flatMap, maka operasi khas dari Applicative ini adalah ap. Dengan sedikit modifikasi, kita bisa melakukan operasi yang jauh lebih readable seperti ini:

/* Kalau menambahkan 2 buah functor */
const add = curry((x, y) => x + y)
const maybe3 = pure(add)
.ap(maybe1)
.ap(maybe2)
/* Kalau menambahkan 3 buah functor */
const add3Nums = curry((x, y, z) => x + y + z)
const maybe6 = pure(add3Nums)
.ap(maybe1)
.ap(maybe2)
.ap(maybe3)
console.log(maybe3, maybe6)
// => Maybe(3), Maybe(6)

Ada 2 kata kunci baru, yang pertama adalah pure, yang kedua adalah ap. Pure hanya membuat argument-nya menjadi sebuah Functor (bisa Maybe, Either, IO, dll). Sehingga jika saya substitusi pure dengan Maybe, maka hasilnya akan sama saja:

const maybe3 = Maybe(add).ap(maybe1).ap(maybe2)

Tanpa currying, hal ini mustahil dilakukan. Karena fungsi add akan dipanggil secara partial dengan dikirimi satu argument per satu pemanggilan .ap. Begitu seterusnya sampai fungsi add benar-benar fully-applied.

Minta implementasi fungsi ap dong mas

Jangan kaget kalau implementasinya sangat sederhana, saking sederhananya saya kagum sama orang yang mencetuskan ide ini pertama kali, kok bisa bikin konsep simple tapi efeknya terasa sekali (lah curhat)

const ApFunctor = value => ({
map: f => ApFunctor(f(value)),
ap: otherFunctor => otherFunctor.map(value)
 ...
})

Sesederhana ini. Fungsi .ap menerima Functor lain yang ingin dikirim isi-nya ke dalam function yang sudah di-pure (contoh di atas adalah fungsi add). Step by stepnya:

const maybe3 = Maybe(add)
.ap(maybe1) // return maybe1.map(x => (y => x + y))
// jalankan map, maka x terisi oleh 1
// return Maybe(y => 1 + y)
.ap(maybe2) // return maybe2.map(y => 1 + y)
// jalankan map, maka y terisi oleh 2
// return Maybe(1 + 2)
// return Maybe(3)

Bisa maen-maen disini kalau yang masih penasaran sama cara kerjanya. Klik aja link di bawah.

Jadi nggak perlu heran kenapa setiap 1 pemanggilan .ap hanya 1 argument saja yang disuplai, karena di dalam .ap sendiri menjalankan operasi .map yang notabene hanya membutuhkan 1 argument saja.

Cara Lain dengan Lift

Ini hanya alternatif saja. Kalau function yang mau kita lift (masukkan ke dalam Functor) memiliki 1 argument, maka aliasnya adalah liftA. Kalau 2 argument maka liftA2, kalau 3 maka liftA3.

Alias menggunakan liftA, agar lebih ringkas

Yup betul ada liftA2, dan liftA3. Agak hard-coded ya. Saya kurang tahu siapa yang menamakan seperti ini pertama kali, tapi saya rasa penamaan ini cukup jelas dan mudah diingat. lift, A (Applicative), 2 (jumlah parameter).

Real World Example

Balik lagi ke contoh kasus offspring saya seperti di 2 artikel sebelumnya. Diberikan 2 buah data:

const munirFam = {
name: 'Munir',
spouse: 'Dian',
child: {
name: 'Jihad',
}
}
const wawanFam = {
name: 'Wawan',
spouse: 'Imi',
child: {
name: 'Puspita',
}
}

Lalu kita ingin menikahkan anak dari keluarga Munir dengan anak dari keluarga Wawan, yaitu Jihad dengan Puspita (This is real, I’m getting married lol). Dan kemudian kita ingin membuat fungsi teks yang akan dibacakan oleh penghulu.

Contoh penghulu

Yap, betul, hasil tidak seusai yang kita harapkan karena yang diterima dari getChild adalah sebuah Functor, sedangkan getName langsung mengakses attribute name. Solusinya sangat mudah seperti yang sudah dijelaskan di atas, yaitu menggunakan Applicative Functor.

Selesai! Sangat simple. Dan jika salah satu keluarga ternyata belum meiliki anak, maka liftA2 akan mengembalikan Nothing.

Manfaat

Ada beberapa keuntungan menggunakan konsep Applicative ini:

  1. Fungsi textPenghulu bisa fokus dengan business logic-nya sendiri. Dalam hal ini adalah string concatenation saja.
  2. Simplicity. Code menjadi lebih concice dengan adanya Applicative.
  3. Untuk mengambil nilai dari satu atau lebih Functor, kita tidak lagi di-pollute oleh operasi buka-tutup Functor. Cukup lift function yang ingin di-apply dan sediakan Functor(s) kita untuk mengubah nilainya.

Intermezzo

Melihat implementasi ap yang sangat sederhana, kita bisa menarik benang merah antara ap dengan map.

Alias operations on Applicative

Namun jika bicara implementasi, karena JavaScript sendiri (ES6) belum support types, maka lebih mudah mengimplementasikan method liftA untuk menggunakan Applicative. Alasannya adalah liftA melakukan map fn pada Functor pertama (argument kedua) yang di-pass. Jadi type-nya akan otomatis mengikuti Functor tersebut. Jika yang di-pass adalah Maybe, maka hasil dari liftA adalah Maybe. Jika Either, maka hasilnya akan Either, dsb.

Contoh yang menggunakan pure di atas saya ambil dari Haskell, namun kurang cocok diterapkan di Javascript. Lebih tepat jika contoh di atas adalah (<$>)-nya Haskell. Dan, fungsi pure idealnya sama dengan liftA yang menerima 2 parameter: Function dan Functor. Jadi saya rekomendasikan menggunakan liftA(n) untuk kemudahan.

Kesimpulan

Sesuai dengan namanya, Applicative Functor adalah Functor yang bisa diaplikasikan. To recall, Function merupakan sesuatu yang bisa di-apply (diaplikasikan, dipanggil). Jika Function yang bisa di-apply ini dimasukkan ke dalam Functor, jadilah Applicative Functor.

Applicative Functor == Functor that holds a function

Penutup

Setelah panjang lebar belajar konsep Applicative (juga Functor & Monad), saya menjadi lebih percaya bahwa programming itu tidak hanya sekedar menulis code yang penting jalan. Kita juga musti jadi programmer yang baik, yang bisa membuat abstraksi dari permasalahan yang ada untuk membuat software yang lebih readable sekaligus maintainable.

Atau malah pingin kayak begini? 😛

Jangan sampai kayak gini haha

Temen-temen punya pendapat lain? Let’s discuss in the comments section below!

If you like the article, please support us by hitting the 💚 button below. Thanks!

--

--

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