Metatable

Mekanisme metatable dan pengaitan perilaku baru pada table melalui __index dan metamethod lainnya.

Daftar isi

Metatable adalah table yang dikaitkan pada sebuah nilai untuk mengubah perilaku operasi tertentu, sedangkan metamethod adalah fungsi atau nilai berawalan dua garis bawah dalam metatable yang merespons operasi tersebut. Topik ini membahas setmetatable dan getmetatable, metamethod __index dalam dua bentuknya, pengendalian penulisan lewat __newindex, serta metamethod aritmetika, perbandingan, dan utilitas seperti __tostring. Mekanismenya bersifat umum, dan pola pemrograman berorientasi objek hanyalah salah satu penerapannya.

Mekanisme metatable#

Setiap table dapat membawa satu metatable, dan metatable itulah yang membuat perilaku bawaan table dapat diganti per peristiwa (event), misalnya pencarian kunci, penjumlahan, atau konversi ke string. Fungsi setmetatable mengaitkan metatable pada sebuah table sekaligus mengembalikan table itu, sedangkan getmetatable membacanya kembali. Selama sebuah peristiwa tidak diatur, perilaku bawaan tetap berlaku seperti biasa. Contoh berikut memasang pencarian pengganti (fallback) lewat __index sehingga kunci yang absen jatuh ke table default.

lua
local default = {tema = "terang", ukuran = 14}

local pengaturan = setmetatable({tema = "gelap"}, {__index = default})

print(pengaturan.tema)
print(pengaturan.ukuran)
print(getmetatable(pengaturan).__index == default)
text Keluaran
gelap
14
true

Kunci tema ditemukan langsung pada pengaturan sehingga isi default tidak disentuh, sedangkan ukuran tidak ada dan pencariannya jatuh ke default. Baris ketiga menunjukkan bahwa metatable hanyalah table biasa yang dapat dibaca dan diubah kembali. Nilai string pun sudah membawa metatable bawaan yang menautkan seluruh fungsi pustaka string lewat __index-nya. Selain table, metamethod juga dapat dipasang pada userdata melalui API C, tetapi pada Lua murni table adalah tipe yang paling sering dimanfaatkan. Daftar lengkap peristiwa tercantum pada bagian 2.4 manual, sedangkan topik ini membatasi diri pada peristiwa yang paling sering digunakan.

Metamethod __index#

Bentuk pertama __index adalah table yang menjadi tujuan pencarian pengganti, sebagaimana default pada bagian sebelumnya. Bentuk kedua adalah fungsi yang menerima table dan kunci yang sedang dicari, lalu nilai baliknya digunakan sebagai hasil pencarian. Bentuk fungsi membuka perilaku dinamis, misalnya nilai default yang dihasilkan saat diminta atau lapisan baca-saja yang menyusul pada bagian berikut. Contoh berikut mengisi semua kunci yang belum ada dengan teks penanda.

lua
local profil = setmetatable({}, {
  __index = function(t, kunci)
    return "(belum diisi)"
  end
})

profil.nama = "Dewi"

print(profil.nama)
print(profil.alamat)
text Keluaran
Dewi
(belum diisi)

Fungsi __index hanya dipanggil untuk alamat karena nama sudah tersimpan sebagai kunci sungguhan. Perilaku dinamis semacam ini bekerja untuk kunci apa pun tanpa perlu mendaftarkannya lebih dahulu, sehingga cocok untuk nilai default yang besar atau yang bentuknya baru diketahui saat diminta.

Metamethod __newindex#

Peristiwa __newindex menyadap pemberian nilai pada kunci yang belum ada. Metamethod ini tidak pernah berlaku untuk kunci yang sudah tersimpan, sehingga penyimpanan yang sah harus dilakukan sendiri dengan rawset di dalamnya. Dua kegunaan yang umum adalah validasi penulisan dan table baca-saja berlapis. Contoh pertama menolak nilai negatif untuk stok barang.

lua
local stok = {}

setmetatable(stok, {
  __newindex = function(t, kunci, nilai)
    if type(nilai) ~= "number" or nilai < 0 then
      error("jumlah stok harus berupa bilangan nonnegatif", 0)
    end
    rawset(t, kunci, nilai)
  end
})

stok.pensil = 100
print(stok.pensil)

local ok, pesan = pcall(function() stok.buku = -5 end)
print(ok, pesan)
text Keluaran
100
false	jumlah stok harus berupa bilangan nonnegatif

Nilai 100 lolos validasi dan disimpan lewat rawset, sedangkan -5 ditolak sehingga pcall menangkap error beserta pesannya. Argumen tingkat 0 pada error menjaga pesan tetap ringkas tanpa tambahan posisi berkas dan baris.

Contoh kedua membungkus table dengan proksi baca-saja. Proksinya kosong, sehingga setiap pembacaan jatuh ke __index menuju table asli dan setiap penulisan pasti tersadap.

lua
local function kunci(t)
  local proksi = {}
  setmetatable(proksi, {
    __index = t,
    __newindex = function()
      error("table ini hanya untuk dibaca", 0)
    end
  })
  return proksi
end

local resep = {nama = "bakwan", banyak_bahan = 4}
local baca = kunci(resep)

print(baca.nama)

local ok, pesan = pcall(function() baca.nama = "perkedel" end)
print(ok, pesan)
text Keluaran
bakwan
false	table ini hanya untuk dibaca

Metamethod aritmetika dan perbandingan#

Peristiwa aritmetika meliputi __add, __sub, __mul, __div, __mod, dan __unm, sedangkan perbandingan menggunakan __eq dan __lt. Metamethod ini menerima operand yang terlibat dan nilai baliknya menjadi hasil operasi, sehingga table dapat berperan layaknya tipe numerik yang lengkap. Kelas Vektor berikut menjumlahkan dan membalik tanda sebuah pasangan koordinat.

lua
local Vektor = {}
Vektor.__index = Vektor

function Vektor.baru(x, y)
  return setmetatable({x = x, y = y}, Vektor)
end

function Vektor.__add(a, b)
  return Vektor.baru(a.x + b.x, a.y + b.y)
end

function Vektor.__unm(a)
  return Vektor.baru(-a.x, -a.y)
end

local p = Vektor.baru(3, 4)
local q = Vektor.baru(1, 2)

print((p + q).x, (p + q).y)
print((-p).x, (-p).y)
text Keluaran
4	6
-3	-4

Ekspresi p + q memanggil __add dengan p dan q sebagai argumen, dan hasilnya berupa Vektor baru sehingga operasi berikutnya dapat dilanjutkan. Perbandingan juga dapat diatur, dan contoh berikut membandingkan pencatatan suhu berdasarkan nilai angkanya.

lua
local Suhu = {}
Suhu.__index = Suhu

function Suhu.baru(nilai)
  return setmetatable({nilai = nilai}, Suhu)
end

function Suhu.__eq(a, b)
  return a.nilai == b.nilai
end

function Suhu.__lt(a, b)
  return a.nilai < b.nilai
end

function Suhu.__tostring(a)
  return a.nilai .. " derajat"
end

local pagi = Suhu.baru(24)
local siang = Suhu.baru(28)
local sore = Suhu.baru(24)

print(pagi == sore)
print(pagi == siang)
print(pagi < siang)
print(siang > pagi)
print(tostring(pagi))
text Keluaran
true
false
true
true
24 derajat

__eq dipanggil karena kedua operand adalah table dengan metatable yang sama. Operator > tidak memiliki metamethod sendiri karena a > b dihitung sebagai b < a, sehingga __lt melayani keduanya. Fungsi __tostring mengubah nilai menjadi teks dan berlaku pula saat nilai itu dicetak dengan print, sebagaimana dibahas pada bagian akhir.

__concat, __call, __tostring, dan __len#

Sisa peristiwa yang sering digunakan menutup daftar ini. Peristiwa __concat mengendalikan operator penggabungan .. ketika salah satu operandnya adalah table, __tostring menentukan bentuk teks saat tostring atau print dipanggil, __len mengatur hasil operator #, sedangkan __call membuat table dapat dipanggil layaknya fungsi. Contoh berikut merangkai tiga peristiwa pertama pada table sensor.

lua
local Uraian = {}
Uraian.__index = Uraian

function Uraian.baru(nama)
  return setmetatable({nama = nama}, Uraian)
end

function Uraian.__tostring(self)
  return "Sensor " .. self.nama
end

function Uraian.__concat(a, b)
  return tostring(a) .. tostring(b)
end

function Uraian.__len(self)
  return #self.nama
end

local a = Uraian.baru("Ruang A")
local b = Uraian.baru("Ruang B")

print(a .. " dan " .. b)
print(tostring(a))
print(#b)
text Keluaran
Sensor Ruang A dan Sensor Ruang B
Sensor Ruang A
7

Penggabungan pertama memanggil __concat karena operand kirinya table, sedangkan operand bertipe string diproses di dalam metamethod melalui tostring. Nilai balik __len bebas dipilih, sehingga operator # pada b menjawab panjang nama dan bukan jumlah field pada table-nya. Peristiwa __call ditutup dengan penghitung sederhana yang bertambah setiap kali table-nya dipanggil.

lua
local Penghitung = {}
Penghitung.__index = Penghitung

function Penghitung.baru(awal)
  return setmetatable({n = awal or 0}, Penghitung)
end

function Penghitung.__call(self, langkah)
  self.n = self.n + (langkah or 1)
  return self.n
end

function Penghitung.__tostring(self)
  return "Penghitung(" .. self.n .. ")"
end

local hitung = Penghitung.baru()

print(hitung())
print(hitung(5))
print(tostring(hitung))
text Keluaran
1
6
Penghitung(6)

Ekspresi hitung() tidak lagi berakhir dengan error karena __call menerima hitung sebagai argumen pertama lalu argumen pemanggilan menyusul setelahnya. Argumen pertama yang berupa table inilah yang pada pola pemrograman berorientasi objek menjadi self.

Ringkasan#

  • Metatable mengaitkan metamethod pada nilai sehingga perilaku operasi bawaan dapat diubah tanpa menyentuh isi table.
  • __index berbentuk table untuk pencarian pengganti atau fungsi untuk perilaku dinamis, dan hanya aktif pada kunci yang tidak ada.
  • __newindex menyadap penulisan kunci baru dan menuntut rawset untuk menyimpan nilai secara langsung.
  • Metamethod aritmetika, perbandingan, __concat, __call, __tostring, dan __len memperluas operasi table hingga menyerupai tipe data baru.

Referensi#