Kotlin adalah bahasa multi-paradigm — kita dapat menulis kode imperatif, object-oriented, atau fungsional. Pada chapter ini kita akan melihat pola desain fungsional yang paling umum dipakai di Kotlin: pipeline map/filter/reduce, function composition, immutability, dan operasi side-effect-free. Kita juga akan membandingkan gaya fungsional Kotlin dengan Haskell murni untuk memahami apa yang bisa dan tidak bisa dilakukan di Kotlin. Pemahaman pola ini membantu kita menulis kode yang lebih modular, mudah diuji, dan kebal terhadap bug akibat mutasi state tak terduga.
Prinsip Utama Functional Design #
Empat prinsip utama desain fungsional yang relevan di Kotlin:
- Immutability — pakai
valdan data classcopy()alih-alih mutasi. - Pure function — function tidak bergantung pada atau mengubah state luar.
- Function composition — gabung function kecil menjadi pipeline.
- Side-effect-free — operasi tidak mengubah input, hanya mengembalikan output baru.
Kotlin tidak mengharuskan prinsip ini secara ketat seperti Haskell, tetapi memilih pola fungsional membantu menulis kode yang lebih jelas dan lebih mudah diuji.
Pola Map/Filter/Reduce #
Pola paling dasar dalam functional design adalah pipeline transformasi koleksi: map mengubah setiap elemen, filter menyaring berdasarkan predikat, dan fold/reduce menggabungkan elemen menjadi satu nilai.
// functional_design_patterns.kt
// Functional design: map/filter/reduce, composition, immutability.
data class Transaksi(val kategori: String, val jumlah: Int, val tipe: String)
fun main() {
val transaksi = listOf(
Transaksi("Makanan", 50_000, "PENGELUARAN"),
Transaksi("Gaji", 5_000_000, "PENDAPATAN"),
Transaksi("Transport", 25_000, "PENGELUARAN"),
Transaksi("Bonus", 1_000_000, "PENDAPATAN"),
Transaksi("Hiburan", 75_000, "PENGELUARAN"),
Transaksi("Makanan", 30_000, "PENGELUARAN")
)
// 1. Pipeline map/filter/reduce — pendekatan fungsional.
val totalPengeluaranMakanan = transaksi
.filter { it.tipe == "PENGELUARAN" && it.kategori == "Makanan" }
.map { it.jumlah }
.fold(0) { acc, n -> acc + n }
println("Total pengeluaran makanan: $totalPengeluaranMakanan")
// 2. Group-by + agregasi (pure functional style).
val ringkasanPerKategori = transaksi
.filter { it.tipe == "PENGELUARAN" }
.groupBy { it.kategori }
.mapValues { (_, daftar) -> daftar.sumOf { it.jumlah } }
println("Ringkasan per kategori:")
ringkasanPerKategori.forEach { (k, v) ->
println(" $k: $v")
}
// 3. Immutability — data class copy, bukan mutasi.
val asli = Transaksi("Makanan", 50_000, "PENGELUARAN")
val diperbarui = asli.copy(jumlah = asli.jumlah + 5_000)
println("Asli: $asli")
println("Diperbarui: $diperbarui")
println("Asli tetap: $asli")
// 4. Function composition untuk pipeline transformasi.
val ambilJumlah: (Transaksi) -> Int = { it.jumlah }
val diskon10persen: (Int) -> Int = { (it * 0.9).toInt() }
val tambahPajak: (Int) -> Int = { (it * 1.11).toInt() }
val proses = ambilJumlah andThen diskon10persen andThen tambahPajak
val contoh = Transaksi("Buku", 100_000, "PENGELUARAN")
println("Hasil transformasi: ${proses(contoh)}")
// 5. Reduce/fold untuk akumulasi.
val daftarAngka = listOf(1, 2, 3, 4, 5)
val total = daftarAngka.fold(0) { acc, n -> acc + n }
val produk = daftarAngka.fold(1) { acc, n -> acc * n }
println("Total: $total, Produk: $produk")
}
// Helper infix untuk function composition.
infix fun <A, B, C> ((A) -> B).andThen(next: (B) -> C): (A) -> C {
return { x -> next(this(x)) }
}Output yang Dihasilkan:
Total pengeluaran makanan: 80000
Ringkasan per kategori:
Makanan: 80000
Transport: 25000
Hiburan: 75000
Asli: Transaksi(kategori=Makanan, jumlah=50000, tipe=PENGELUARAN)
Diperbarui: Transaksi(kategori=Makanan, jumlah=55000, tipe=PENGELUARAN)
Asli tetap: Transaksi(kategori=Makanan, jumlah=50000, tipe=PENGELUARAN)
Hasil transformasi: 99900
Total: 15, Produk: 120Mari kita telaah baris-baris kode di atas:
- Baris 6 (
data class Transaksi(val kategori: String, val jumlah: Int, val tipe: String)): data class dengan propertyval— immutable. Tidak adavar, sehingga instanceTransaksitidak dapat dimutasi setelah dibuat. - Baris 16-20 (
val totalPengeluaranMakanan = transaksi.filter { ... }.map { ... }.fold(0) { ... }): pipeline map/filter/reduce.filtermenyaring transaksi pengeluaran makanan,mapmengambil propertyjumlah,foldmenjumlahkan dari accumulator0. Tidak ada loop manual atau mutasi. - Baris 23-27 (
transaksi.filter { ... }.groupBy { ... }.mapValues { ... }): group-by + agregasi.filtermenyaring pengeluaran,groupBymengelompokkan per kategori menjadiMap<String, List<Transaksi>>,mapValuesmengubah setiap value menjadi total jumlah. Hasil adalahMap<String, Int>. - Baris 30-33 (
val diperbarui = asli.copy(jumlah = asli.jumlah + 5_000)): immutability via copy.aslitetap tidak berubah;copymembuat instance baru denganjumlahyang dimodifikasi. Pola ini adalah inti immutability di Kotlin. - Baris 36-42 (
val ambilJumlah = ...; val diskon10persen = ...; val tambahPajak = ...): tiga function kecil, masing-masing satu tanggung jawab. Function-function ini akan digabung menjadi pipeline. - Baris 44 (
val proses = ambilJumlah andThen diskon10persen andThen tambahPajak): function composition. OperatorandThenmenggabungkan tiga function menjadi satu function(Transaksi) -> Int. Pemanggilanproses(contoh)menjalankan ketiganya secara berurutan. - Baris 48-50 (
val total = daftarAngka.fold(0) { ... }; val produk = daftarAngka.fold(1) { ... }): fold untuk akumulasi.foldmenerima initial value dan lambda(acc, n) -> acc. Total memakai initial0dan operasi+; produk memakai initial1dan operasi*.
Immutability dengan val dan copy #
Immutability adalah prinsip yang didukung Kotlin melalui val dan data class copy. Alih-alih memutasi property, kita membuat instance baru dengan perubahan yang diinginkan.
data class Pengguna(val nama: String, val umur: Int)
val asli = Pengguna("Budi", 25)
val ultah = asli.copy(umur = 26)
// asli tetap Pengguna("Budi", 25) — tidak berubahManfaat immutability: aman dipakai di coroutine tanpa race condition, mudah diuji, dan history perubahan dapat dilacak.
Kotlin vs Haskell #
Kotlin bukan bahasa fungsional murni seperti Haskell. Berikut perbandingan singkat:
| Aspek | Kotlin | Haskell |
|---|---|---|
| Paradigma | Multi-paradigm (OOP + FP) | Pure functional |
| Immutability | Default dengan val | Default untuk semua |
| Side effect | Diizinkan | Dibatasi via IO monad |
| Lazy evaluation | Memerlukan Sequence | Default |
| Pattern matching | when expression | Bawaan, lebih ekspresif |
Side-Effect-Free Pipeline #
Pola pipeline fungsional bersih dari side effect — setiap langkah hanya menghasilkan output baru tanpa mengubah input atau state luar:
val hasil = transaksi
.filter { it.tipe == "PENGELUARAN" }
.map { it.jumlah }
.sortedDescending()
.take(3)
.sum()
// Tidak ada variabel yang dimutasi; transaksi tetap utuh.Pipeline seperti ini mudah diuji, mudah diparalelkan, dan kebal terhadap bug mutasi.
Referensi Lanjutan #
- Higher-order functions and lambdas
— dasar pola fungsional
map/filter/reduce - Inline functions
— efisiensi pola fungsional via
inline - Scope functions — kombinasi scope function dalam pola fungsional
Penutup #
Pola desain fungsional — map/filter/reduce, function composition, immutability, dan side-effect-free pipeline — membuat kode Kotlin lebih modular, mudah diuji, dan kebal terhadap bug akibat mutasi state. Walaupun Kotlin bukan bahasa fungsional murni seperti Haskell, kita dapat mengadopsi prinsip fungsional pada bagian kode yang paling membutuhkan kejelasan dan keandalan. Dengan menguasai lambdas, scope functions, dan pola fungsional, kita siap menulis kode Kotlin modern yang ringkas, ekspresif, dan andal di produksi.