Lua memperlakukan fungsi sebagai nilai kelas satu (first-class), yakni nilai yang dapat disimpan dalam variabel, dikirim sebagai argumen, dan dikembalikan dari fungsi lain. Kemampuan tersebut melahirkan dua pola yang menjadi ciri kode Lua yang matang, yaitu closure, yakni fungsi yang membawa lingkungan leksikalnya, dan iterator, yakni fungsi yang menghasilkan nilai berurutan satu per satu. Topik ini membahas cara fungsi dalam menangkap variabel lokal fungsi luarnya, cara state, yaitu keadaan yang tersimpan antarpemanggilan, dipertahankan tanpa variabel global, serta cara protokol perulangan for generik menjalankan fungsi iterasi. Setelah membacanya, penghitung antrean, penyaring daftar nilai ujian, dan iterator teks dapat dibangun mengikuti pola bawaan bahasa.
Closure dan lingkungan leksikalnya#
Sebuah closure adalah fungsi beserta lingkungan leksikalnya, yaitu kumpulan variabel lokal yang berada pada ruang lingkup tempat fungsi itu didefinisikan. Apabila sebuah fungsi didefinisikan di dalam fungsi lain, fungsi dalam tetap dapat mengakses variabel lokal fungsi luarnya meskipun fungsi luar telah selesai dieksekusi. Variabel yang tertangkap semacam ini disebut upvalue, yakni variabel lokal yang dirujuk oleh fungsi dalam. Masa hidup upvalue mengikuti masa hidup closure yang merujuknya, sehingga keadaan dapat tersimpan dengan aman tanpa menambah variabel global.
Contoh klasiknya adalah penghitung antrean tiket loket. Fungsi pembuatPenghitung mendefinisikan variabel lokal nilai, lalu mengembalikan fungsi naik yang menambah nilai dan mengembalikan hasil barunya. Simpan berkas berikut di samping index.md, jalankan dengan lua penghitung.lua, lalu amati bahwa loketA dan loketB bertambah secara terpisah.
-- penghitung.lua
-- Closure menyimpan state lewat variabel local pada fungsi luarnya.
local function pembuatPenghitung(mulai)
local nilai = mulai
local function naik(langkah)
nilai = nilai + (langkah or 1)
return nilai
end
return naik
end
local loketA = pembuatPenghitung(0)
local loketB = pembuatPenghitung(100)
print("A", loketA())
print("A", loketA())
print("B", loketB())
print("A", loketA(5))A 1
A 2
B 101
A 7Dua pemanggilan pembuatPenghitung menghasilkan dua instansi closure yang berdiri sendiri. Setiap instansi membawa variabel nilai masing-masing, sehingga penambahan pada loketA tidak pernah menggeser loketB, yang memang dimulai dari 100. Ekspresi langkah or 1 pada fungsi naik menunjukkan idiom Lua untuk menggantikan argumen nil dengan nilai awal. Pola ini berlaku umum, bukan hanya untuk angka, karena closure dapat menangkap table, string, maupun fungsi lain.
Closure sebagai penyaring dan akumulator#
Karena closure merupakan nilai biasa, closure dapat dikirim sebagai argumen, disimpan di dalam table, atau dijadikan nilai balik sebuah fungsi pembuat. Bentuk yang sering muncul adalah fungsi predikat (predicate), yaitu fungsi yang menjawab benar atau salah untuk satu kriteria tertentu. Fungsi pembuat menerima kriteria sekali pada saat pembuatan, sedangkan closure yang dihasilkan menggunakan kriteria itu pada setiap pemanggilan berikutnya.
Contoh berikut menyaring daftar nilai ujian dengan batas kelulusan 75 dan sekaligus menjumlahkan nilai yang lulus. Fungsi pembuatPenyaring menangkap batas yang tidak pernah berubah, sedangkan pembuatAkumulator menangkap total yang terus diperbarui. Keduanya menunjukkan bahwa upvalue dapat bersifat tetap maupun berubah antarpemanggilan.
-- penyaring.lua
-- Closure untuk menyaring daftar nilai ujian dan menjumlahkan yang lulus.
local function pembuatPenyaring(batas)
return function(nilai)
return nilai >= batas
end
end
local function pembuatAkumulator()
local total = 0
return function(nilai)
total = total + nilai
return total
end
end
local nilaiUjian = {72, 85, 60, 90, 78, 55}
local lulus = pembuatPenyaring(75)
local jumlahkan = pembuatAkumulator()
for _, nilai in ipairs(nilaiUjian) do
if lulus(nilai) then
print(nilai, "lulus", "total", jumlahkan(nilai))
end
end85 lulus total 85
90 lulus total 175
78 lulus total 253Fungsi jumlahkan mengingat total dari pemanggilan sebelumnya. Pemanggilan pertama menjadikan total menjadi 85, pemanggilan kedua menambahkannya menjadi 175, dan pemanggilan ketiga menjadi 253. Apabila akumulator kedua dibuat dari pembuatAkumulator, akumulator tersebut memulai perhitungan dari nol lagi karena menerima variabel total yang baru. Pola penyaring dan akumulator semacam ini lazim digunakan pada pemrosesan daftar nilai, catatan log sensor, maupun agregasi sederhana lainnya.
Protokol iterator pada for generik#
Sebuah iterator adalah fungsi yang menghasilkan elemen berurutan satu per satu, biasanya satu elemen per pemanggilan. Perulangan for generik adalah fasilitas bahasa yang menjalankan iterator semacam itu. Bentuk lengkapnya adalah for v1, v2 in eksp1, eksp2, eksp3 do, dengan eksp1 berupa fungsi iterasi, eksp2 berupa keadaan yang tetap, dan eksp3 berupa nilai kontrol awal. Lua mengevaluasi ketiga ekspresi itu sekali sebelum perulangan dimulai, lalu pada setiap iterasi memanggil fungsi iterasi dengan dua argumen, yaitu nilai eksp2 dan nilai kontrol terkini. Perulangan berhenti bila nilai balik pertama berupa nil, sedangkan nilai balik pertama yang bukan nil menjadi nilai kontrol berikutnya.
Contoh berikut menjalankan protokol itu secara langsung tanpa closure agar mekanismenya terlihat. Fungsi iterasi mengabaikan keadaan, menaikkan nilai kontrol, dan berhenti setelah nilai ketiga.
-- protokol-for.lua
-- Perulangan for generik memanggil fungsi iterasi dengan tiga nilai.
local function iterasi(state, kontrol)
kontrol = kontrol + 1
if kontrol <= 3 then
return kontrol, "data-" .. kontrol
end
return nil
end
-- bentuk lengkap: for var-list in eksp1, eksp2, eksp3 do
for nilai, label in iterasi, nil, 0 do
print(nilai, label)
end1 data-1
2 data-2
3 data-3Diagram berikut merangkum pertukaran nilai antara for generik dan fungsi iterasinya, termasuk cara perulangan berhenti.
Fungsi bawaan pairs dan ipairs mengikuti protokol yang sama. Keduanya mengembalikan fungsi iterasi beserta keadaan dan nilai kontrol awalnya, sehingga dapat langsung dipasangkan dengan for generik. Perbedaannya terletak pada sumber data, yaitu ipairs menelusuri table secara berurutan mulai dari indeks 1, sedangkan pairs menelusuri seluruh kunci tanpa jaminan urutan.
Fungsi iterasi tidak harus menerima keadaan lewat argumen. Apabila fungsi iterasinya berupa closure, daftar dan indeks dapat ditangkap langsung sebagai upvalue, sehingga pemanggilan for hanya membutuhkan satu ekspresi. Contoh berikut membangun iterator kata dari sebuah teks dengan menangkap teks dan posisi sebagai upvalue.
-- pengulang-kata.lua
-- Iterator berbasis closure untuk memecah teks menjadi kata satu per satu.
local function pengulangKata(teks)
local posisi = 1
return function()
local awal, akhir = string.find(teks, "%S+", posisi)
if awal == nil then
return nil
end
posisi = akhir + 1
return string.sub(teks, awal, akhir)
end
end
for kata in pengulangKata("antrean tiket loket pagi") do
print(kata)
endantrean
tiket
loket
pagiFungsi hasil pengulangKata mengabaikan dua argumen protokol karena seluruh keadaannya berada pada upvalue, sehingga penulisan for kata in pengulangKata(teks) do tidak memerlukan ekspresi kedua dan ketiga. Pencarian kata di dalam contoh menggunakan pola "%S+" dari pustaka string, yang mencocokkan rangkaian karakter bukan spasi.
Ringkasan#
- Closure adalah fungsi yang membawa lingkungan leksikalnya, sehingga variabel lokal fungsi luar dapat menjadi keadaan yang tersimpan tanpa variabel global.
- Setiap pemanggilan fungsi pembuat menghasilkan instansi closure yang berdiri sendiri beserta upvalue milik instansi tersebut.
- Perulangan
forgenerik memanggil fungsi iterasi dengan dua argumen, yaitu keadaan dan nilai kontrol, lalu berhenti bila nilai balik pertamanyanil. pairsdanipairsmerupakan iterator bawaan yang mengikuti protokol yang sama, sedangkan iterator khusus dapat dibangun dari closure.
Referensi#
- Manual Referensi Lua 5.4 — Lua.org — Bagian 3.3.5 tentang perulangan for, bagian 3.5 tentang aturan cakupan, dan bagian 6.1 tentang definisi fungsi menjadi dasar protokol iterator dan closure.
- Programming in Lua, edisi keempat — Lua.org — Bab 6 membahas fungsi dan bab 7 membahas iterator serta closure pada versi daringnya.