Functional Programming: Dependency Injection menggunakan Reader Monad 🎊

with the help of Typescript

Jihad Dzikri Waspada
codewey
6 min readApr 26, 2018

--

Injection (source)

Salam semua! Kembali lagi di episode Functional Programming dengan Javascript! Yeeaaay πŸŽ‰πŸŽ‰πŸŽ‰. Namun kali ini kita akan coba perlahan menggunakan Typescript, karena gak akan pernah afdhol kalo bicara FP tapi gak menyinggung types-nya 😁

Apa dan Mengapa DI?

Bagi yang belum tahu apa itu DI, bisa dibaca dulu apa sih Dependency Injection dan mengapa DI itu penting dalam Software Development. Scope artikel ini hanya mengulas bagaimana melakukan DI the functional way, itu juga kalo bener πŸ˜† (mohon koreksinya suhu Wisnu Adi Nurcahyo πŸ™πŸ»)

Apa: design patterns β€” What is dependency injection? β€” Stack Overflow

Mengapa:

Intinya: DI memungkinkan component/class/function yang kita buat tidak bergantung pada 1 instance tertentu saja, sehingga bisa di-substitute dengan instance yang lain ➑️ more flexibility.

Nah, bicara tentang Dependency Injection, sebenarnya banyak kok cara melakukan DI di Javascript. Cara paling mudah adalah dengan inject dependencies-nya ke class constructor seperti yang dicontohkan di artikel Mas Wahyudi Wibowo barusan:

class UserRepo {
constructor(db) { // <- HERE
this.db = db
}
... save(user) {
this.db.insert(user)
}
...}

Atau cara kedua seperti yang Mattias terangkan di video ini? (alert: 22 mins video)

Kesimpulan ada di bawah

Yang kurang lebih, bisa saya simpulkan seperti:

const save = (user, db) => db.insert(user) // atau
const save = user => db => db.insert(user)
saveUserToDb = save(user);
...
...
saveUserToDb(db)

Sebenarnya ndak ada masalah, mungkin dengan cara di atas sudah cukup. Yang penting jalan kan hehe. Tapi muncul pertanyaan: apakah solusi tersebut sudah mendukung composability? Karena tujuan menggunakan pendekatan Functional Programming salah satunya adalah membuat program yang composable, sehingga kode bisa menjadi lebih rapi dan reusable. Dan, cara paling mudah untuk membuktikannya adalah dengan membahas sebuah case study.

Case Study

Sebut saja Bambang (bukan nama sebenarnya), dia menjual gorengan menggunakan boraks. Ermm, nggak. Sesungguhnya dia adalah seorang nasabah dari sebuah bank bernama Bank Krut. Karena dia jomblo dan kere karena pake boraks, dia memutuskan untuk menabung hasil jerih payahnya di bank tersebut untuk modal pesta pernikahannya. Transaksi Bambang terhitung irit, kamu bisa lihat betapa bahagianya dia:

Transaksi Bambang

Oh iya, buat kamu-kamu yang masih kadang bingung bedanya debit sama credit (kayak gue πŸ˜“) debit itu nabung, credit itu narik duid.

Okay, balik ke pembahasan. Ada beberapa issue yang terlihat dari kode barusan:

  1. Terlalu verbose. Orang yang nanti akan menggunakan fungsi-fungsi tersebut harus menyuplai dependency satu per satu. Terasanya belakangan ketika sudah banyak fungsi yang terlibat.
  2. Sulit untuk di-compose. Seperti yang kita ketahui, salah satu keuntungan menggunakan pendekatan FP adalah composability-nya.

Adakah cara lain agar kodenya tidak terlalu verbose namun tetap bisa melakukan komposisi?

Reader Monad!

Reader Monad bisa menjadi salah satu alternatif untuk permasalahan ini. Walaupun gak harus jadi silver bullet, ada baiknya kita memahami konsep Reader Monad ini dan melihat apakah bisa menawarkan solusi yang lebih baik. Buat temen-temen yang sudah lupa atau belum tahu apa itu Monad, bisa di-recall dulu apa sih definisi dan kegunaan Monad

Nah setelah fresh dengan method fmap dan bind, kita bisa lanjut membahas Reader Monad yang ternyata simple tapi cukup bikin dahi mengernyit. Dan sebelum masuk ke solusi sekaligus contoh pemakaiannya, kita akan coba mempelajari bagaimana membuat Reader Monad from scratch.

Step 1: See the Pattern

Mari kita mulai: langkah pertama dalam mecari suatu solusi adalah dengan melihat pola atau pattern dari masalah yang ingin dipecahkan. Dalam hal ini, pola yang terlihat adalah banyaknya pemanggilan terhadap dependency-nya ...(repo). Bila kita scroll sedikit ke atas dan merenunginya (awas ada mantan lewat), inti dari fungsi-fungsi tersebut adalah menerima dependency di argumen-nya, kemudian mengembalikan suatu nilai, sebut saja A.

const fungsi = <A>(deps: Dependencies): A => { ... }

Mari kita simpan notasi ini dan sepakat menyebutnya dengan nama Reader, sesuatu yang akan membaca/menggunakan dependencies (environment).

Reader Interface

So, untuk kasus transaksiBambang ini, bisa kita pahami

const transaksiBambang = (no: string) => (deps: D) => { return A }const transaksi = transaksiBambang(no)
// transaksi: (deps: D) => { return A }
// transaksi adalah Reader<D, A>

Step 2: Add fmap Function!

Karena Monad merupakan salah satu turunan dari Functor, wajib hukumnya untuk menuliskan implementasi fmap. Masih ingatkan notasi dari fmap?

fmap notation

So, bisa kita mulai dari return type-nya dulu. Karena return type-nya adalah Reader<D, B> yang merupakan alias dari (deps: D) => B , maka:

fmap = (f, readerA) => Reader<D, B>
fmap = (f, readerA) => ((deps: D) => B)

Sekarang nilai B. f memiliki type A => B yang berarti dengan melakukan f(A) kita akan mendapatkan nilai B.

fmap = (f, readerA) => ((deps: D) => B)
fmap = (f, readerA) => ((deps: D) => f(A))

Dan tentu saja nilai A didapat dari pemanggilan readerA(deps) melihat type-nya yang readerA: (deps: D) => A.

fmap = (f, readerA) => ((deps: D) => f(A))
fmap = (f, readerA) => ((deps: D) => f(readerA(deps)))

Sampai sini dapat disimpulkan fungsi fmap untuk Reader Monad adalah:

fmap untuk Reader

IT”S EASY! πŸŽ‰πŸŽ‰

Part 3: BIND! The Fun Part

Sekarang saatnya kita membuat Reader ini menjadi Monad dengan mengimplementasikan fungsi bind. Method bind pada Monad memiliki type signature:

Bind type-annotation

Hampir tidak ada perbedaan: return type-nya sama-sama Reader<D, B>. Yang membedakan hanya fungsi f-nya saja (dibahas sebentar lagi). Namun memiliki return type yang sama: Reader<D, B> a.k.a (deps: D) => B.

bind = (f, readerA) => ((deps: D) => B)

Sebelumnya pada fungsi fmap, f(reader(deps)) akan menghasilkan B.

fmap = (f, readerA) => ((deps: D) => f(reader(deps)))

Sedangkan pada bind, f(reader(deps)) tidak lagi menghasilkan B, melainkan Reader<D, B>, alias (deps: D) => B. Sehingga untuk mendapatkan nilai B, kita hanya perlu menyuplainya dengan (deps: D) sekali lagi.

bind = (f, readerA) => ((deps: D) => f(reader(deps))(deps))

Type-nya sudah benar (bisa dicek sendiri dengan Typescript), artinya kita telah selesai membuat implementasi bind pada Reader Monad! πŸŽ‰πŸŽŠ

bind untuk reader

Balik ke Contoh

Kita sudah tau implementasi map dan bind pada Reader.

fmap & bind

atau versi yang rada OOP dikit biar gampang nanti chaining-nya:

Reader Monad written as Object

Biar gak scroll ke atas lagi, saya tuliskan ulang salah satu fungsi dari contoh kita di awal artikel.

debit(no: string, amount: number): (accountRepo: AccountRepo) => Account

Bisa kita lihat bagian yang saya bold, itu artinya fungsi debit ini memiliki dependency bertipe AccountRepo dan nilai kembaliannya bertipe Account. Dengan Reader Monad, type signature fungsi ini bisa kita aliaskan menjadi

debit(no: string, amount: number): Reader<AccountRepo, Account>

Keduanya sama saja. Kita gak akan mengubah apapun. Yang ingin kita ubah hanya implementasi fungsi transaksiBambang.

Reader!

Kode di atas bisa kita refactor sedikit. Kita bisa lihat bahwa fungsi debit, credit, dan balance tidak bergantung pada hasil komputasi sebelumnya, mereka hanya bergantung pada no dan amount. Saya pindahkan closure di composeK ke masing-masing fungsi tersebut.

Refined version

Bagi yang penasaran sama fungsi composeK, bisa mampir dulu~

Dengan melihat fungsi transaksiBambang, kita tidak lagi melihat dependency accountRepo yang dioper secara eksplisit satu per satu. Namun, jika kita intip type signature-nya, kita baru bisa mengetahui bahwa fungsi ini memiliki dependency terhadap AccountRepo:

const transaksiBambang: (no: string) => Reader<AccountRepo, number>

Untuk lebih mengetahui kegunaan Reader Monad dari segi komposisinya, saya berikan satu contoh lagi yang lebih simple. Misal saya punya Dependency Container seperti

selanjutnya bisa kita perhatikan semua fungsi di bawah ini adalah hasil komposisi terhadap fungsi lainnya, hanya dengan menggunakan konsep Reader Monad:

Komposisi Reader Monad

Note: semua fungsi dia atas bertipe Reader<Deps, …> πŸ˜ƒ

Kesimpulan

Reader Monad memungkinkan kita untuk β€œmenyembunyikan” dependencies ketika melakukan komposisi sehingga kita bisa fokus kepada komposisinya, kepada mengatur business logic-nya, tanpa perlu pusing-pusing passing dependency satu per satu. Ketika kita ingin mengetahui apa saja dependency yang dibutuhkan, kita hanya perlu melihat type signature dari fungsi yang membutuhkan dependency tersebut. Sehingga bisa saya sebut Reader Monad ini ada di tengah-tengah: tidak terlalu eksplisit dan verbose, dependency ter-declare secara jelas, tidak juga melakukan β€œmagic” dalam me-resolve dependencies (Yes, I’m looking at you, Laravel).

Poin menarik lainnya yang bisa dipetik adalah bahwa ternyata sebuah fungsi bisa kita buat menjadi Monad. Seperti yang telah kita perlajari, Reader Monad ini hanyalah sebuah fungsi yang menerima 1 argumen dan mengembalikan suatu nilai: a function in its simplest form. Tapi dengan mengikuti hukum Functor dan Monad seperti yang kita lakukan bersama barusan, berubahlah dia jadi Dependency Injector πŸ˜… (kalo liat di sini sih, Reader Monad bisa juga banyak implementasinya).

Masih ada beberapa istilah lagi tentang Reader Monad yang sebenernya ingin saya tulis (seperti ask dan local), tapi kayaknya udah cukup panjang artikelnya. Kalo panjang-panjang, ntar si Bambang udah keburu nikah. Insyaallah kalo udah ngerti dasar konsep Reader ini, ask dan local akan sangat mudah dimengerti.

Anyway, semoga tulisan ini bermanfaat. Kalo ada bagian yang kurang tepat, mohon koreksinya. Karena saya sendiri juga baru belajar konsep ini. Sama-sama berbagi ya πŸ™ƒ. Hatur nuhun. Salaam.

References:

  1. https://blog.ssanj.net/posts/2014-09-23-A-Simple-Reader-Monad-Example.html
  2. http://blog.originate.com/blog/2013/10/21/reader-monad-for-dependency-injection/
  3. https://www.youtube.com/watch?v=xPlsVVaMoB0
  4. https://www.youtube.com/watch?v=AkOFubm-9L8

--

--

Jihad Dzikri Waspada
codewey
Editor for

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