let dan imutabilitas

Binding let yang imutabel secara baku serta kata kunci mutable untuk kebutuhan yang memang beralasan.

Daftar isi

Topik ini membahas binding let sebagai mekanisme utama F# untuk mengaitkan nama dengan nilai, disertai sifat imutabel yang menjadi pilihan bawaan bahasa. Pembahasannya bergerak dari penulisan binding yang benar, reaksi compiler ketika nilai imutabel mencoba diubah, kata kunci mutable beserta operator panah <-, hingga pertimbangan kapan mutasi layak digunakan. Setelah topik ini dibaca, penulisan binding yang benar serta keputusan antara let dan mutable sudah dapat dilakukan dengan dasar yang jelas.

Binding let mengikat nama ke nilai#

Binding let mengaitkan sebuah nama dengan sebuah nilai secara tetap, tanpa anotasi tipe dan tanpa kata kunci tambahan. Bentuknya sederhana, yaitu kata let, nama, tanda sama dengan, lalu ekspresi nilainya. Tipe data tidak dituliskan karena compiler menyimpulkannya dari ekspresi yang mengikuti tanda sama dengan, sebuah kemampuan yang dibahas lebih rinci pada topik inferensi tipe. Baris let umur = 24 pada contoh berikut sudah cukup membuat umur menjadi nilai bertipe bilangan bulat.

fsharp binding-let.fsx
// Binding let mengikat nama ke sebuah nilai secara permanen
let nama = "Citra"
let umur = 24
let tinggiCm = 162.5
let aktif = true

printfn "Nama    : %s" nama
printfn "Umur    : %d tahun" umur
printfn "Tinggi  : %f cm" tinggiCm
printfn "Aktif   : %b" aktif

// Nama baru dapat dihitung dari nama yang sudah terikat
let tinggiMeter = tinggiCm / 100.0
printfn "Tinggi  : %f meter" tinggiMeter
text Keluaran
Nama    : Citra
Umur    : 24 tahun
Tinggi  : 162.500000 cm
Aktif   : true
Tinggi  : 1.625000 meter

Setiap baris let pada contoh di atas menghasilkan satu pengikatan yang berlaku dari baris itu ke bawah. Nilai pada nama bertipe string karena literalnya berupa teks, sedangkan tinggiCm bertipe float karena literalnya memuat tanda desimal. Binding baru juga boleh dihitung dari binding lama, seperti tinggiMeter yang diturunkan dari tinggiCm melalui operasi pembagian. Pola semacam ini lazim dijadikan gaya penulisan sehari-hari karena setiap langkah perhitungan diberi nama yang menjelaskan maksudnya.

Nilai imutabel menolak pengubahan#

Nilai yang diikat dengan let bersifat imutabel (immutable) secara bawaan, sehingga mengubahnya lewat kode bukan hal yang diperbolehkan. Operator panah <- memang ada di F#, tetapi operator itu hanya berlaku pada nilai yang dinyatakan mutable. Apabila <- diterapkan pada binding let biasa, compiler menolak skrip tersebut pada saat pemeriksaan tipe. Contoh skrip berikut sengaja memuat percobaan pengubahan tersebut.

fsharp reassignment.fsx
// tidak-dijalankan: skrip ini dieksekusi lewat perintah terminal pada blok berikutnya
// percobaan mengubah nilai imutabel dengan panah <- ditolak compiler
let skor = 80
skor <- 95
printfn "Skor: %d" skor
bash
# menjalankan skrip yang berisi percobaan pengubahan nilai imutabel
# jalur berkas pada pesan error dipangkas agar barisnya ringkas
dotnet fsi reassignment.fsx 2>&1 | sed "s|.*/||"
text Keluaran
reassignment.fsx(4,1): error FS0027: This value is not mutable. Consider using the mutable keyword, e.g. 'let mutable skor = expression'.

Pesan error di atas memang diharapkan dan menunjukkan perilaku yang benar. Compiler memeriksa seluruh skrip sebelum satu baris pun dijalankan, sehingga printfn pada baris terakhir tidak pernah dieksekusi dan tidak ada keluaran lain selain pesan errornya. Pesan tersebut menyebut kode FS0027 dan sekaligus menawarkan perbaikannya, yaitu menulis let mutable skor = expression apabila perubahan nilai memang diinginkan. Kode keluar proses ini juga bukan nol, sehingga skrip semacam ini mudah terdeteksi pada alur kerja otomatis. Pada percobaan langsung di terminal, jalur lengkap berkas skrip tampil di depan pesan, dan blok di atas memangkasnya dengan sed agar ringkas.

mutable dan panah <-#

Kata kunci mutable pada sebuah binding let menciptakan sel nilai yang isinya boleh diganti melalui operator panah <-. Kata kunci ini ditulis tepat setelah let, yaitu let mutable nama = nilaiAwal. Pembacaan nilai tetap menggunakan nama biasa tanpa simbol apa pun, sedangkan penggantian nilai menggunakan nama <- nilaiBaru. Contoh berikut menghitung rekap nilai ujian dari lima mahasiswa dengan dua penghitung yang berkembang selama perulangan.

fsharp mutable-panah.fsx
// Kata kunci mutable membuat satu sel nilai yang boleh diubah
let mutable jumlahLulus = 0
let mutable totalNilai = 0

for nilai in [80; 45; 75; 90; 60] do
    if nilai >= 75 then
        jumlahLulus <- jumlahLulus + 1
    totalNilai <- totalNilai + nilai

printfn "Jumlah lulus : %d dari 5" jumlahLulus
printfn "Total nilai  : %d" totalNilai
printfn "Rata-rata    : %f" (float totalNilai / 5.0)
text Keluaran
Jumlah lulus : 3 dari 5
Total nilai  : 350
Rata-rata    : 70.000000

Kedua penghitung dimulai dari nol, lalu diisi ulang pada setiap putaran perulangan dengan operator <-. Nilai jumlahLulus bertambah satu setiap kali nilai ujian mencapai batas kelulusan, sedangkan totalNilai menampung penjumlahan seluruh nilai. Perubahan itu terjadi pada sel yang sama, bukan pada pengikatan nama baru, sehingga nilainya benar-benar berganti dari putaran ke putaran. Perlu dicatat bahwa perilaku ini berbeda dari mengikat ulang nama dengan let, yang tidak mengubah nilai lama melainkan memperkenalkan pengikatan baru pada lingkup yang lebih dalam. Pembacaan kembali nama yang sama selalu menghasilkan isi sel terbaru, bukan nilai awalnya.

Kapan mutable masuk akal#

Aturan praktisnya, imutabilitas digunakan sebagai pilihan baku dan mutable digunakan hanya apabila perubahan nilai memang merupakan inti dari proses yang ditulis. Pilihan bawaan ini bukan sekadar selera penulisan. Nilai imutabel dapat diteruskan ke fungsi mana pun tanpa risiko berubah di tengah jalan, sehingga asal sebuah hasil tidak perlu dilacak ke bagian lain program. Beberapa situasi yang lazim membenarkan mutasi antara lain berikut.

  • Penghitung dan agregator sederhana di dalam perulangan, seperti rekap nilai pada contoh di atas.
  • Interop (interoperability) dengan pustaka .NET yang antarmukanya menuntut perubahan objek, misalnya pengisian buffer secara bertahap.
  • Optimasi pada bagian kode yang dieksekusi sangat sering dan terbukti menjadi hambatan performa.

Di luar situasi tersebut, penjumlahan daftar, pemetaan nilai, dan rekapitulasi data sebaiknya ditulis dengan fungsi koleksi yang menerima dan mengembalikan nilai baru. Gaya demikian menghasilkan alur data yang dapat diikuti tanpa melacak perubahan diam-diam, dan compiler tetap menjaga tipe setiap langkahnya.

Ringkasan#

  • let mengikat nama ke nilai secara tetap dan tanpa anotasi tipe, karena tipenya disimpulkan compiler.
  • Nilai hasil let bersifat imutabel, dan <- pada nilai tersebut memicu error FS0027 sebelum program berjalan.
  • let mutable menciptakan sel yang boleh diganti dengan <-, cocok untuk penghitung, interop, dan optimasi yang memang beralasan.

Referensi#