Sejak Kotlin 1.9.x, JetBrains memperkenalkan context receiver sebagai fitur eksperimental — cara untuk menyuntik beberapa receiver implisit ke dalam satu fungsi. Pengalaman menunjukkan bahwa context receiver punya keterbatasan: tidak bisa diberi nama, tidak mendukung default argumen, dan pesan kesalahan kompilator sering membingungkan. Pada Kotlin 2.2+, JetBrains menggantinya dengan context parameter — sintaks baru yang lebih ekspresif: context(value: Type). Pada chapter ini kita akan melihat konsep context parameter, membandingkannya dengan context receiver lama, dan menulis pola manual yang setara karena kompilator 2.0.21 belum mendukung sintaks resminya.
Mengapa Context Parameter Diperlukan #
Saat kita menulis fungsi yang butuh beberapa dependensi (mis. Logger, Konfigurasi, Tracer), pendekatan tradisional adalah menerima semuanya sebagai parameter eksplisit. Pendekatan ini verbose — pemanggil harus menyebutkan semua parameter walaupun banyak di antaranya adalah detail implementasi. Context parameter menyelesaikan masalah ini dengan menyembunyikan dependensi yang sudah tersedia di scope, sehingga pemanggil tidak perlu menyebutkannya ulang.
Sintaks resmi di Kotlin 2.2+ terlihat seperti berikut:
interface Logger { fun info(pesan: String) }
interface Konfigurasi { val verbose: Boolean }
// `logger` dan `konfig` adalah context parameter
context(logger: Logger, konfig: Konfigurasi)
fun simpan(data: String): String {
if (konfig.verbose) logger.info("Menyimpan: $data")
return "saved:$data"
}
// Pemanggilan: kompilator mencari logger & konfig di scope pemanggil
fun main() {
val logger: Logger = ...
val konfig: Konfigurasi = ...
with(logger, konfig) {
simpan("halo") // logger & konfig otomatis terisi
}
}Karena kompilator 2.0.21 yang dipakai di buku ini belum mendukung sintaks tersebut, kita menulis pola manual yang setara — passing dependensi sebagai parameter eksplisit atau memakai receiver class. Hasil runtime sama, tetapi boilerplate lebih panjang.
Pola Manual Setara (Kompatibel 2.0.21) #
Cuplikan berikut menunjukkan tiga pendekatan manual yang menghasilkan perilaku sama dengan context parameter. Kita pakai pendekatan ini di kode produksi yang masih ditargetkan ke Kotlin 2.0:
// context_parameters.kt
//
// Mendemonstrasikan konsep "Context Parameters" (Kotlin 2.2+).
// Sintaks:
// context(value: T)
// fun foo() { ... }
//
// Context parameter menggantikan "context receiver" (eksperimental di 1.9.x).
// Fitur ini BELUM tersedia di kotlinc 2.0.21 (perlu 2.2+).
//
// File ini mendemonstrasikan dua hal:
// (a) Pola manual yang setara — passing parameter eksplisit (kompatibel 2.0.21)
// (b) Pola receiver-class (extension function) sebagai alternatif
//
// Lihat index.md untuk sintaks context parameter sesungguhnya.
// 1. Pendekatan manual (kompatibel 2.0.21): operasi butuh Logger dan Konfigurasi
interface Logger {
fun info(pesan: String)
}
interface Konfigurasi {
val mode: String
val verbose: Boolean
}
class LoggerStdout : Logger {
override fun info(pesan: String) = println("[INFO] $pesan")
}
class KonfigurasiProduksi(
override val mode: String = "production",
override val verbose: Boolean = false
) : Konfigurasi
// Tanpa context parameter: lewatkan logger dan konfigurasi sebagai argumen
fun simpanDataManual(
data: String,
logger: Logger,
konfigurasi: Konfigurasi
): String {
if (konfigurasi.verbose) logger.info("Menyimpan data: $data")
val hasil = "saved:$data"
logger.info("Selesai: $hasil")
return hasil
}
// 2. Pendekatan receiver: jadikan logger sebagai receiver implicit
class KonteksLogger(private val logger: Logger, private val konfig: Konfigurasi) {
fun simpan(data: String): String {
if (konfig.verbose) logger.info("Menyimpan: $data")
val hasil = "saved:$data"
logger.info("Selesai: $hasil")
return hasil
}
}
// 3. Extension function untuk "menyisipkan" context sebagai receiver
fun Logger.denganKonfig(konfig: Konfigurasi, block: Logger.() -> Unit) {
this.block()
}
// 4. Builder-style context — kombinasi logger dan konfigurasi
class Lingkungan(val logger: Logger, val konfig: Konfigurasi) {
inline fun <T> dengan(block: Lingkungan.() -> T): T = this.block()
fun info(pesan: String) = logger.info(pesan)
fun verbose() = konfig.verbose
}
fun main() {
println("=== Context Parameters (Konsep) ===")
println("Fitur `context(value: T)` tersedia di Kotlin 2.2+")
println("Pada 2.0.21, gunakan pendekatan manual atau receiver.")
println()
println("--- 1. Pendekatan manual (passing parameter eksplisit) ---")
val logger = LoggerStdout()
val konfig = KonfigurasiProduksi(verbose = true)
val hasil1 = simpanDataManual("halo", logger, konfig)
println("Hasil: $hasil1")
println()
println("--- 2. Pendekatan receiver class ---")
val konteks = KonteksLogger(logger, konfig)
val hasil2 = konteks.simpan("dunia")
println("Hasil: $hasil2")
println()
println("--- 3. Extension function sebagai context ---")
logger.denganKonfig(konfig) {
info("Memulai operasi via receiver implicit")
}
println()
println("--- 4. Builder-style context (mirip `with`/`run`) ---")
val lingkungan = Lingkungan(logger, konfig)
val hasil3 = lingkungan.dengan {
if (verbose()) info("verbose aktif")
info("Operasi inti berjalan")
"ok"
}
println("Hasil builder: $hasil3")
println()
println("--- 5. Sintaks context parameter (Kotlin 2.2+) ---")
println(" context(logger: Logger, konfig: Konfigurasi)")
println(" fun simpan(data: String): String {")
println(" if (konfig.verbose) logger.info(\"Simpan: \$data\")")
println(" return \"saved:\$data\"")
println(" }")
println()
println("Pemanggilan:")
println(" with(logger, konfig) {")
println(" simpan(\"halo\")")
println(" }")
println()
println("--- 6. Perbandingan context receiver vs context parameter ---")
val perbandingan = listOf(
"Context receiver (1.9.x, eksperimental): `context(Logger, Konfig)` di signature",
"Context parameter (2.2+): `context(logger: Logger, konfig: Konfig)` — eksplisit",
"Context parameter mendukung default argumen, vararg, dan rename",
"Migrasi: ganti `context(T)` jadi `context(t: T)`"
)
perbandingan.forEach { println(" - $it") }
}Output yang Dihasilkan:
=== Context Parameters (Konsep) ===
Fitur `context(value: T)` tersedia di Kotlin 2.2+
Pada 2.0.21, gunakan pendekatan manual atau receiver.
--- 1. Pendekatan manual (passing parameter eksplisit) ---
[INFO] Menyimpan data: halo
[INFO] Selesai: saved:halo
Hasil: saved:halo
--- 2. Pendekatan receiver class ---
[INFO] Menyimpan: dunia
[INFO] Selesai: saved:dunia
Hasil: saved:dunia
--- 3. Extension function sebagai context ---
[INFO] Memulai operasi via receiver implicit
--- 4. Builder-style context (mirip `with`/`run`) ---
[INFO] verbose aktif
[INFO] Operasi inti berjalan
Hasil builder: ok
--- 5. Sintaks context parameter (Kotlin 2.2+) ---
context(logger: Logger, konfig: Konfigurasi)
fun simpan(data: String): String {
if (konfig.verbose) logger.info("Simpan: $data")
return "saved:$data"
}
Pemanggilan:
with(logger, konfig) {
simpan("halo")
}
--- 6. Perbandingan context receiver vs context parameter ---
- Context receiver (1.9.x, eksperimental): `context(Logger, Konfig)` di signature
- Context parameter (2.2+): `context(logger: Logger, konfig: Konfig)` — eksplisit
- Context parameter mendukung default argumen, vararg, dan rename
- Migrasi: ganti `context(T)` jadi `context(t: T)`Pendekatan pertama (simpanDataManual) paling sederhana tetapi membebani setiap pemanggil dengan tiga parameter. Pendekatan kedua (KonteksLogger) memakai satu receiver class untuk membungkus dependensi — lebih bersih tetapi tetap butuh objek pembungkus. Context parameter menghilangkan kedua beban tersebut.
Perbandingan Context Receiver vs Context Parameter #
Sebelum context parameter stabil, Kotlin 1.9.x memperkenalkan context receiver dengan sintaks:
context(Logger, Konfigurasi)
fun simpan(data: String): String { ... }Tabel berikut merangkum perbedaan utama:
| Aspek | Context Receiver (1.9.x) | Context Parameter (2.2+) |
|---|---|---|
| Sintaks deklarasi | context(Logger, Konfig) | context(logger: Logger, konfig: Konfig) |
| Penamaan | tidak bisa | bisa (rename bebas) |
| Default argumen | tidak didukung | didukung |
| Vararg | tidak didukung | didukung |
| Resolusi ambigu | sering membingungkan | eksplisit lewat nama |
Kapan Memakai Context Parameter #
Context parameter cocok untuk situasi berikut:
- Fungsi yang butuh banyak cross-cutting concern (logging, tracing, metric).
- DSL builder yang ingin mengakses konteks parent tanpa passing parameter eksplisit.
- Test fixture yang butuh
TestScope,CoroutineDispatcher, danMockKinstance. - Library yang menyediakan banyak fungsi util dengan dependensi implisit yang sama.
Hindari context parameter untuk hal berikut:
- API publik yang dipakai lintas modul (membuat signature tidak jelas).
- Fungsi satu parameter tanpa dependensi tambahan (terlalu berat).
- Situasi yang lebih cocok memakai extension function biasa.
Referensi Lanjutan #
- What’s new in Kotlin 2.0 — ringkasan fitur Kotlin 2.x termasuk kemunculan context parameters sebagai pengganti context receiver
- Kotlin documentation home — halaman utama dokumentasi Kotlin untuk memantau status fitur eksperimental context parameters
Selanjutnya #
Setelah memahami context parameter sebagai fitur masa depan, kita mundur ke fitur yang sudah stabil di 2.0.21 — data object — yang memberi singleton dengan toString/equals/hashCode otomatis tanpa boilerplate override manual.