Gaya Idiomatik

Fungsi scope let, run, apply, also, dan with dengan pertimbangan yang tepat.

Daftar isi

Kotlin menyediakan lima fungsi scope, yaitu let, run, apply, also, dan with, yang menjalankan satu blok kode pada sebuah objek tertentu. Kelimanya sering terlihat saling menggantikan, sehingga pemilihan yang keliru masih lolos compiler, tetapi gaya penulisannya menjadi tidak konsisten. Topik ini menjalankan kelima fungsi scope pada satu objek konfigurasi yang sama, membaca dua perbedaan pokoknya langsung dari keluaran program, yaitu bentuk konteks objek di dalam blok dan nilai yang dikembalikan, lalu menutup dengan tabel pertimbangan pemilihannya.

Lima fungsi scope pada satu objek#

Program berikut memakai satu objek KonfigurasiRapor yang diisi, dibaca, dicatat, dan diperiksa dengan kelima fungsi scope secara bergantian. Setiap panggilan mencetak hasilnya ke terminal, sehingga perbedaan antarfungsi terlihat tanpa perlu membayangkan perilakunya. Objeknya berupa data class sederhana dengan tiga properti yang dapat diubah, dan seluruh pemrosesan terjadi pada satu instans yang sama, sehingga perubahan yang dibuat oleh satu fungsi scope terbawa ke pemanggilan berikutnya.

kotlin main.kt
data class KonfigurasiRapor(
    var judul: String = "",
    var semester: String = "",
    var batasLulus: Int = 0
)

fun main() {
    // apply: konteks this, hasil = objek konteksnya sendiri
    val konfig = KonfigurasiRapor().apply {
        judul = "Rapor Kelas 7A"
        semester = "Ganjil"
        batasLulus = 75
    }
    println("apply : $konfig")

    // with: konteks this, hasil = nilai lambda
    val ringkasan = with(konfig) {
        "$judul, semester $semester, batas lulus $batasLulus"
    }
    println("with  : $ringkasan")

    // let: konteks it, hasil = nilai lambda
    val panjangJudul = konfig.let { it.judul.length }
    println("let   : $panjangJudul")

    // run: konteks this, hasil = nilai lambda
    val status = konfig.run {
        if (batasLulus in 1..100) "batas lulus valid" else "batas lulus tidak valid"
    }
    println("run   : $status")

    // also: konteks it, hasil = objek konteksnya sendiri
    val hasilAlso = konfig.also {
        println("also  : mencatat ${it.judul} sebelum dipakai")
    }
    println("also  : hasil = objek yang sama: ${hasilAlso === konfig}")
}
bash
# compiler Kotlin dan pustaka standar yang dipakai jalur ini
export PATH=/home/kal/my-project/tools/kotlinc/bin:/home/kal/my-project/tools/jdk-merged/bin:$PATH
export STDLIB=/home/kal/my-project/tools/kotlinc/lib/kotlin-stdlib.jar

# kompilasi main.kt menjadi out.jar, lalu jalankan programnya
kotlinc main.kt -d out.jar && java -cp "out.jar:$STDLIB" MainKt
text Keluaran
apply : KonfigurasiRapor(judul=Rapor Kelas 7A, semester=Ganjil, batasLulus=75)
with  : Rapor Kelas 7A, semester Ganjil, batas lulus 75
let   : 14
run   : batas lulus valid
also  : mencatat Rapor Kelas 7A sebelum dipakai
also  : hasil = objek yang sama: true

Bagian apply memperlihatkan pengisian tiga properti tanpa awalan nama objek, karena di dalam bloknya objek tersedia sebagai this. Nilai kembaliannya adalah objek itu sendiri, sehingga val konfig langsung memegang instans yang sudah terisi. Bagian with mengelompokkan tiga pembacaan properti menjadi satu ekspresi dan mengembalikan string yang disusun darinya. Bagian let dan run keduanya menghitung nilai baru dari objek, hanya saja let menyediakan objeknya sebagai parameter it, sedangkan run menyediakannya sebagai this. Bagian also menyisipkan pencatatan di tengah alur, lalu mengembalikan objek yang sama persis, dan hal itu dibuktikan oleh baris terakhir keluaran yang mencetak true dari perbandingan identitas dengan operator ===.

Konteks objek dan nilai kembalian#

Seluruh perbedaan kelima fungsi scope dirangkum oleh dua sumbu. Sumbu pertama adalah bentuk konteks objek, yaitu apakah objeknya tersedia di dalam blok sebagai this yang berperan sebagai receiver atau sebagai parameter lambda it. Sumbu kedua adalah nilai kembalian, yaitu apakah fungsi scope mengembalikan hasil lambdanya sendiri atau mengembalikan objek konteksnya. Tabel berikut menyusun kelima fungsi pada kedua sumbu tersebut.

FungsiKonteks objekNilai kembalianSituasi lazim
letithasil lambdamengubah sebuah nilai menjadi nilai lain, termasuk penanganan null
runthishasil lambdamenghitung nilai baru dari beberapa properti objek
withthishasil lambdamengelompokkan panggilan pada objek yang sudah tersedia
applythisobjek konteksmengisi objek baru saat dibuat
alsoitobjek konteksmenyisipkan efek samping seperti pencatatan

Keluaran program memberi bukti konkret untuk setiap sel pada kolom nilai kembalian. Baris with, let, dan run mencetak nilai yang sama sekali baru, yaitu string, bilangan bulat, dan status pemeriksaan, sehingga ketiganya terbukti mengembalikan hasil lambda. Baris apply mencetak objek konfigurasinya sendiri, sedangkan baris also ditutup dengan true, yang menunjukkan bahwa objek yang dikembalikan adalah instans yang sama, bukan salinannya.

Kesalahan umum#

Kesalahan pertama adalah memilih apply padahal nilai baru yang dibutuhkan. Hasil kembalian apply tetap berupa objek konteksnya, sehingga val status = konfig.apply { ... } berisi objek konfigurasi, bukan hasil perhitungan di dalam bloknya. Penyebabnya adalah pembacaan kode yang hanya melihat bentuk bloknya mirip, tanpa memperhatikan kolom nilai kembalian. Perbaikannya adalah memindahkan perhitungan tersebut ke run atau let agar variabelnya berisi nilai yang diharapkan.

Kesalahan kedua adalah menumpuk fungsi scope secara berlapis, misalnya let di dalam apply di dalam also. Pada susunan seperti itu, rujukan it yang bertumpuk sulit dibedakan, dan campuran antara this milik blok luar dengan blok dalam membuat pembaca harus menghitung tingkat sarangnya. Perbaikannya adalah memberi nama pada parameter lambda, misalnya also { konfig -> ... }, atau memecah rantai menjadi variabel bernama.

Ringkasan#

  • Lima fungsi scope berbeda pada dua hal, yaitu konteks objek di dalam blok (this atau it) dan nilai kembaliannya (hasil lambda atau objek konteks).
  • apply dan also mengembalikan objek konteksnya, sehingga keduanya cocok untuk pengisian objek dan efek samping di tengah alur.
  • let, run, dan with mengembalikan hasil lambda, sehingga ketiganya cocok untuk menghitung nilai baru dari objek.
  • Pemilihan fungsi scope adalah keputusan gaya penulisan, karena kelima fungsi dapat menghasilkan program yang berperilaku sama.

Referensi#