also adalah scope function yang menggunakan it sebagai receiver dan mengembalikan objek itu sendiri. Berbeda dari apply yang memakai this, also memakai parameter implisit it sehingga kode di dalam block tetap eksplisit tentang objek yang sedang diproses. Pada chapter ini kita akan melihat kapan also lebih cocok daripada apply — terutama untuk side effect seperti logging dan debugging — serta bagaimana also membantu menjaga chain tetap jelas saat menyisipkan operasi tanpa mengubah nilai.
Apa Itu also #
Signature also:
inline fun <T> T.also(block: (T) -> Unit): T- Receiver: objek yang dipanggil
also(tersedia sebagaiit). - Return: objek itu sendiri (
T). - Use case utama: side effect (logging, debugging) tanpa mengganggu chain.
Tabel Perbandingan also dengan Scope Lain #
| Aspek | also |
|---|---|
| Receiver | it (parameter implisit) |
| Return | objek itu sendiri |
| Idiomatic use | Side effect (log, debug) |
| Block return value | Diabaikan (Unit) |
Bisa rename it | Ya — also { x -> ... } |
Pola also di Kode Nyata #
Mari kita lihat empat pola also yang sering muncul.
// also_example.kt
// also: receiver `it`, mengembalikan objek.
// Cocok untuk side effect (logging, debug) tanpa mengganggu chain.
data class Pesanan(val id: Int, var nominal: Int, var status: String = "BARU")
fun main() {
// Pola 1: logging dengan also tanpa mengubah alur.
val pesanan = Pesanan(101, 50_000)
.also { println("[LOG] Membuat pesanan: $it") }
.apply { status = "DIPROSES" }
.also { println("[LOG] Pesanan diproses: $it") }
.apply { nominal += 5_000 } // biaya tambahan
.also { println("[LOG] Nominal diperbarui: ${it.nominal}") }
println("Hasil akhir: $pesanan")
// Pola 2: also untuk validasi side effect.
val daftar = mutableListOf<String>()
val hasil = daftar.also {
println(" Status awal: ${it.size} elemen")
}.apply {
add("Belajar")
add("Kotlin")
}.also {
println(" Status akhir: ${it.size} elemen")
}
println("Daftar: $hasil")
// Pola 3: also untuk debugging di tengah chain.
val angka = listOf(1, 2, 3, 4, 5)
val hasilHitung = angka
.also { println(" Input: $it") }
.map { it * 2 }
.also { println(" Setelah map: $it") }
.filter { it > 4 }
.also { println(" Setelah filter: $it") }
.sum()
println("Total: $hasilHitung")
// Pola 4: rename `it` untuk kejelasan.
val nama = "Budi"
val hasilRename = nama.also { n ->
println(" Memproses nama: $n")
}
println("Hasil: $hasilRename")
}Output yang Dihasilkan:
[LOG] Membuat pesanan: Pesanan(id=101, nominal=50000, status=BARU)
[LOG] Pesanan diproses: Pesanan(id=101, nominal=50000, status=DIPROSES)
[LOG] Nominal diperbarui: 55000
Hasil akhir: Pesanan(id=101, nominal=55000, status=DIPROSES)
Status awal: 0 elemen
Status akhir: 2 elemen
Daftar: [Belajar, Kotlin]
Input: [1, 2, 3, 4, 5]
Setelah map: [2, 4, 6, 8, 10]
Setelah filter: [6, 8, 10]
Total: 24
Memproses nama: Budi
Hasil: BudiMari kita telaah baris-baris kode di atas:
- Baris 6 (
data class Pesanan(val id: Int, var nominal: Int, var status: String = "BARU")): data class dengan beberapa property. Kondisi ideal untuk demonstrasi chainalso+apply. - Baris 10-19 (
val pesanan = Pesanan(101, 50_000).also { ... }.apply { ... }.also { ... }...): pola logging dengan also. Chainalsodanapplyberselang-seling —alsomencatat log,applymengubah property.alsotidak mengubah nilai yang mengalir;applymemodifikasi objek. Hasil akhir adalah objekPesananyang sudah dimodifikasi. - Baris 21-22 (
Pesanan(101, 50_000).also { println("[LOG] Membuat pesanan: $it") }):alsomencetak instancePesanansaat dibuat.$itadalah objek tersebut. Block returnUnit,alsomengembalikan objekPesananitu sendiri. - Baris 25-30 (
val daftar = mutableListOf<String>().also { ... }.apply { ... }.also { ... }): pola validasi side effect.alsomencatat ukuran daftar sebelum dan sesudahapplymengisi elemen. Berguna untuk debugging chain yang kompleks. - Baris 33-40 (
val hasilHitung = angka.also { ... }.map { ... }.also { ... }.filter { ... }.also { ... }.sum()): pola debugging di tengah chain. Setiapalsomencetak nilai yang mengalir di antara transformasi. Berguna saat menelusuri bug pada pipeline koleksi. - Baris 43-46 (
val hasilRename = nama.also { n -> println(...) }): pola renameit. Bilaitkurang jelas, beri nama deskriptifn. Berguna saat body side effect memerlukan konteks yang jelas.
also vs apply #
also dan apply sama-sama mengembalikan objek itu sendiri. Perbedaan utama adalah akses ke receiver:
| Aspek | apply | also |
|---|---|---|
| Receiver | this | it |
| Idiomatic use | Konfigurasi (mengubah property) | Side effect (log, debug) |
| Sintaks | apply { foo = 1 } (tanpa prefix) | also { println(it) } (eksplisit) |
also untuk Debugging #
Saat menelusuri bug pada chain transformasi koleksi, also dapat menyisipkan log di antara langkah tanpa mengubah alur:
val hasil = data
.filter { it.aktif }
.also { println("Setelah filter: ${it.size}") }
.map { transform(it) }
.also { println("Setelah map: ${it}") }
.sum()Saat bug sudah diperbaiki, hapus also tanpa mengubah logika chain.
Referensi Lanjutan #
- Scope functions
— panduan resmi
alsountuk side effect tanpa mengubah receiver - Higher-order functions and lambdas
— dasar lambda
itpadaalso
Penutup #
also adalah scope function untuk side effect tanpa mengganggu alur chain. Dengan it sebagai receiver dan kembalian objek itu sendiri, also cocok untuk logging, debugging, dan observasi. Dengan memisahkan apply (untuk mengubah) dan also (untuk mengamati), kode chain menjadi jelas tentang langkah mana yang memodifikasi objek. Selanjutnya, kita akan menggabungkan semua konsep lambdas dan scope functions ke dalam pola desain fungsional — map/filter/reduce, composition, dan immutability — pada chapter Functional Design Patterns.