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:

kotlin Signature also
inline fun <T> T.also(block: (T) -> Unit): T
  • Receiver: objek yang dipanggil also (tersedia sebagai it).
  • Return: objek itu sendiri (T).
  • Use case utama: side effect (logging, debugging) tanpa mengganggu chain.

Tabel Perbandingan also dengan Scope Lain #

Aspekalso
Receiverit (parameter implisit)
Returnobjek itu sendiri
Idiomatic useSide effect (log, debug)
Block return valueDiabaikan (Unit)
Bisa rename itYa — also { x -> ... }

Pola also di Kode Nyata #

Mari kita lihat empat pola also yang sering muncul.

kotlin
// 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:

text
[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: Budi

Mari kita telaah baris-baris kode di atas:

  1. Baris 6 (data class Pesanan(val id: Int, var nominal: Int, var status: String = "BARU")): data class dengan beberapa property. Kondisi ideal untuk demonstrasi chain also + apply.
  2. Baris 10-19 (val pesanan = Pesanan(101, 50_000).also { ... }.apply { ... }.also { ... }...): pola logging dengan also. Chain also dan apply berselang-seling — also mencatat log, apply mengubah property. also tidak mengubah nilai yang mengalir; apply memodifikasi objek. Hasil akhir adalah objek Pesanan yang sudah dimodifikasi.
  3. Baris 21-22 (Pesanan(101, 50_000).also { println("[LOG] Membuat pesanan: $it") }): also mencetak instance Pesanan saat dibuat. $it adalah objek tersebut. Block return Unit, also mengembalikan objek Pesanan itu sendiri.
  4. Baris 25-30 (val daftar = mutableListOf<String>().also { ... }.apply { ... }.also { ... }): pola validasi side effect. also mencatat ukuran daftar sebelum dan sesudah apply mengisi elemen. Berguna untuk debugging chain yang kompleks.
  5. Baris 33-40 (val hasilHitung = angka.also { ... }.map { ... }.also { ... }.filter { ... }.also { ... }.sum()): pola debugging di tengah chain. Setiap also mencetak nilai yang mengalir di antara transformasi. Berguna saat menelusuri bug pada pipeline koleksi.
  6. Baris 43-46 (val hasilRename = nama.also { n -> println(...) }): pola rename it. Bila it kurang jelas, beri nama deskriptif n. 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:

Aspekapplyalso
Receiverthisit
Idiomatic useKonfigurasi (mengubah property)Side effect (log, debug)
Sintaksapply { 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:

kotlin also untuk debugging chain
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 #

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.