13 Metatablolar ve Metametotlar

13 Metatablolar ve Metametotlar

Genelde, Lua'daki her değer oldukça tahmin edilebilir  işlem kümesine sahiptir. Sayıları toplayabilir, stringleri birleştirebilir, tablolara anahtar-değer çiftlerini ekleyebiliriz vb. Ancak tabloları toplayamaz, fonksiyonları karşılaştıramaz ve bir stringe çağrı yapamayız.

Metatablolar, tanımsız bir işlemle karşı karşıya kalındığında bir değerin davranışını değiştirmemize imkan verir. Örneğin metatabloları kullanarak, a ve b tablolar olmak üzere, a+b ifadesini Luan'nın nasıl hesaplayacağını tanımlayabiliriz. Lua iki tabloyu toplamaya giriştiğinde onlardan birinin bir metatabloya sahip olup olmadığını ve bu metatablonun bir __add alanına sahip olup olmadığını kontrol eder. Lua bu alanı bulursa toplamı hesaplayacak bir fonksiyon olan metametot adı verilen ilgili değeri çağırır.

Lua 'daki her değer metatabloya sahip olabilir. Tablolar ve userdata kendi özel metatablolarına sahiptir; diğer türlerin değerleri bu türün tüm değerleri için tek bir metatabloyu paylaşır (Lua 5.0'da sadece tablolar ve userdata metatablolara sahip olabiliyordu. Ekseriye zaten metatablolar ile kontrol etmek istediğimiz türler bunlar). Lua yeni tabloları  daima metatabloları olmadan oluşturur:

t = {}
print(getmetatable(t)) --> nil

Herhangi bir tabloya metatablo eklemek veya değiştirmek için setmetatable'ı kullanabiliriz:

t1 = {}
setmetatable(t, t1)
assert(getmetatable(t) == t1)

Herhangi bir tablo herhangi bir değerin metatablosu olabilir; ilgili tablolar grubu,  ortak davranışları betimleyen ortak bir metatablo paylaşabilir; bir tablo kendi özgün davranışını betimleyen kendi metatablosuna da sahip olabilir. Herhangi yapılandırma geçerlidir.

Lua 'dan sadece tabloların metatablolarını set edebiliriz; Diğer türdeki değerlerin metatablolarını manipüle etmek için C kodu kullanmamız gerekir. (Bu kısıtlamanın ana nedeni, tip bazında metatablolaların aşırı kullanımını engellemektir. Lua'nın eski sürümlerindeki deneyimler sıklıkla, yeniden kullanılabilir olmayan kodlamalara yol açtığını göstermiştir.) Daha sonra göreceğimiz üzere, bölüm 20'de string kütüphanesi stringler için metatablolar set eder. Diğer tüm tipler varsayılanda  metatabloya sahip değildir:

print(getmetatable("hi"))   --> table: 0x80772e0
print(getmetatable(10))     --> nil

13.1 Aritmetik Metametotlar

Bu bölümde metatabloların nasıl kullanılacağını açıklamak için basit bir örnek sunacağız. İki kümenin birleşimini, kesişimini  hesaplayacak fonksiyonlarla, kümeleri temsil etmek için tabloları kullandığımızı varsayalım. isim alanımızı temiz tutmak için bu fonksiyonları Set adlı bir tablo içinde depoluyoruz:

Set = {}

-- verilen listenin değerleriyle yeni bir küme yarat
function Set.new(l)
    local set = {}
    for _, v in ipairs(l) do set[v] = true end
    return set
end

function Set.union(a, b)
    local res = Set.new {}
    for k in pairs(a) do res[k] = true end
    for k in pairs(b) do res[k] = true end
    return res
end

function Set.intersection(a, b)
    local res = Set.new {}
    for k in pairs(a) do
        res[k] = b[k]
    end
    return res
end

Örneklerimizi kontrol etmeye yardımcı olması için kümeleri yazdıracak bir fonksiyon tanımlıyoruz aynı zamanda:

function Set.tostring(set)
    local l = {} -- kümeden tüm elemanların konulacağı liste
    for e in pairs(set) do
        l[#l + 1] = e
    end
    return "{" .. table.concat(l, ", ") .. "}"
end

function Set.print(s)
    print(Set.tostring(s))
end

Şimdi, toplama operatörünün ('+') iki kümenin birleşimini hesaplamasını istiyoruz. Bunun için, toplama operatörüne nasıl reaksiyon gösterileceğini tanımlayacak bir metatabloyu paylaşacak kümeleri temsil eden tüm tabloları ayarlayacağız. İlk adımımız, kümeler için metatablo olarak kullanacağımız klasik bir tablo oluşturmak:

local mt = {} -- kümeler için metatablo

Bir sonraki adım, kümeleri oluşturan Set.new fonksiyonunu modifiye etmek. Yeni sürüm, yalnızca bir ekstra satıra sahip; oluşturulan tablolar için metatablo olarak mt'nin set edilmesi:

function Set.new(l) -- 2. sürüm
    local set = {}
    setmetatable(set, mt)
    for _, v in ipairs(l) do
        set[v] = true
    end
    return set
end

Bundan sonra, Set.new ile oluşturduğumuz her küme metatablosu olarak aynı tabloya sahip olacak:

s1 = Set.new {10, 20, 30, 50}
s2 = Set.new {30, 1}
print(getmetatable(s1)) --> table: 00672B60
print(getmetatable(s2)) --> table: 00672B60

Son olarak, metatabloya, toplamanın nasıl gerçekleşeceğini tanımlayan metametotu, __add alanını ekliyoruz:

mt.__add = Set.union

Bundan sonra Lua iki kümeyi toplamaya giriştiğinde, argümanları olarak iki operatörle set.union fonksiyonunu çağırır.

Devamdaki gibi metametot ile, küme birleşimi yapmak için '+' operatörünü kullanabilirsiniz:

s3 = s1 + s2
Set.print(s3) --> {1, 10, 20, 30, 50}

Benzer şekilde, küme kesişimini gerçekleştirmek için '*' işlecini-operatörünü ayarlayabiliriz:

mt.__mul = Set.intersection

Set.print((s1 + s2)*s1) --> {10, 20, 30, 50}

Her aritmetik operatör için, metatabloda karşılık gelen bir alan adı vardır. __add ve __mul haricindekiler  __sub (çıkarma için), __div (bölme için), __unm (negatifleştirme için), __mod (mod için), ve __pow (üs için). birleştirme işleci(..) için davranış tanımlamak için __concat alanınıda tanımlayabiliriz.

İki kümeyi topladığımızda hangi metatablonun kullanılacağı hakkında bir şüphe yok. Bununla birlikte örneğin farklı metatablolulu iki değeri  miks eden bir ifade yazabiliriz:

s = Set.new{1,2,3}
s = s + 8

Lua bir metametota bakarken devamdaki adımları gerçekleştirir: Eğer ilk değer __add alanlı bir metatabloya sahipse, ikinci değerden bağımsız olarak , Lua metametot olarak bu alanı kullanır; aksi durumda, ikinci değer __add alanlı metatabloya sahipse, Lua metametot olarak bu alanı kullanır; aksi halde, Lua hata yükseltir. Bu nedenle son örnek 10+s ve "hello"+s ifadeleri olacak şekilde Set.union 'ı çağırır.

Lua bu miks türler hakkında endişe taşımaz ancak uygulamamız taşır. s=s+8 örneğini çalıştırırsak, aldığımız hata Set.union içinde olacaktır:

bad argument #1 to 'pairs' (table expected, got number)

Daha net hata iletileri istiyorsak, işlemi gerçekleştirmeye girişmeden önce işleç türlerini açıkça kontrol etmeliyiz:

function Set.union (a, b)
    if getmetatable(a) ~= mt or getmetatable(b) ~= mt then
        error("attempt to ’add’ a set with a non-set value", 2)
    end
    <önceki gibi>

error 'e ikinci argüman (bu örnekte 2) hata mesajını işlemin çağrıldığı yere yönlendirdiğini hatırlayın.

13.2 İlişkisel Metametotlar

Metatablolar ayrıca, __eq (eşit), __lt (küçük) ve __le (eşit veya küçük) metametotlarıyla, ilişkisel operatörlere anlam yüklememize imkan verir. Devamdaki üç ilişkisel operatör için ayrı metametotlar yoktur, Lua 'nın çevrimi ile  a~=b      not (a==b) 'e ,  a>b      b<a 'e ve  a>=b      b<=a 'e.

Lua 4.0'a kadar tüm sıralama operatörleri  teke çevirilirdi, a<=b not (b<a)'e gibi. Bununla birlikte, kısmi sıralamaya sahip olduğunda bu çeviri yanlıştır, yani tipimizdeki tüm öğeler tam bir şekilde sıralanmadığında. Örneğin, sayı olmayan bir değer (NaN) sebebiyle kayan nokta sayıları çoğu makinede tam olarak sıralanmaz. IEEE 754 standardına göre, şu anda hemen hemen tüm kayan nokta donanımları tarafından kabul edilen NaN tanımsız bir değeri temsil eder , 0/0 sonucu gibi. Standart, herhangi bir karşılaştırmada NaN'ın false olarak sonuçlanması gerektiğini belirtir. Bu, NaN<=x 'nin daima false olduğu anlamına gelir, tabi x<NaN 'da false'dir. Ayrıca, a<= b 'den not (b<a) çeviri bu durumda geçerli değildir.

kümeler örneğimizde benzer bir sorunumuz var. kümelerde <= anlamı, küme kapsamadır: a<=b,  a b'nin bir alt kümesi olduğu anlamına gelir. Bu anlamla, hem a<=b hem de b<a'nın false olması mümkündür; bu nedenle, __le (eşit veya küçük) ve __lt(küçük) için ayrı uyarlamalara ihtiyacımız var:

mt.__le = function(a, b) -- küme kapsam
    for k in pairs(a) do
        if not b[k] then
            return false
        end
    end
    return true
end

mt.__lt = function(a, b)
    return a <= b and not (b <= a)
end

Nihayetinde, küme kapsama ile eşit kümeyi tanımlayabilirsiniz:

mt.__eq = function(a, b)
    return a <= b and b <= a
end

Bu tanımlamalardan sonra kümeleri karşılaştırmaya hazırız:

s1 = Set.new {2, 4}
s2 = Set.new {4, 10, 2}
print(s1 <= s2)     --> true
print(s1 < s2)      --> true
print(s1 >= s1)     --> true
print(s1 > s1)      --> false
print(s1 == s2 * s1)--> true

Aritmetik metametotların aksine, ilişkisel metametotlar miks tiplere uygulanamaz. Miks tipler için davranışlar,  Lua'daki bu operatörlerin için olan ortak davranışları taklit eder. sıralama için bir stringi  bir sayı ile karşılaştırmaya çalışırsanız, Lua bir hata yükseltir. Benzer şekilde, sıralamak için farklı metametotlu iki nesneyi   karşılaştırmaya çalışırsanız Lua bir hata yükseltir.

Eşitlik karşılaştırması asla bir hata yükseltmez ancak iki nesne farklı metametotlara sahipse eşitlik işlemi false ile sonuçlanır, herhangi bir metametot çağırmadan bile. Yine bu davranış, değerleri ne olursa olsun stringleri sayılardan farklı olarak sınıflandıran Lua'nın ortak davranışını taklit eder.
Lua, eşitlik metametotunu yalnızca karşılaştırılan iki nesne metametotu paylaştığında çağırır.

13.3 Kütüphane-Tanımlı Metametotlar

Metatablolarda kendi alanlarını tanımlama, kütüphaneler için yaygın bir uygulamadır. Şimdiye kadar gördüğümüz tüm metametotlar Lua çekirdeği için olanlar. Bir işlemde yer alan değerlerin metatabloları olduğu ve bu metatabloların o işlem için metametotlar tanımlandığını tespit eden sanal makinedir.  tabi, metatablolar klasik tablolar olduğundan, herkes onları kullanabilir.

tostring fonksiyonu tipik bir örnek sağlar. Daha önce gördüğümüz üzere, tostring tabloları oldukça basit bir formatta temsil eder:

print({}) --> table: 0x8062ac0

(print fonksiyonu her zaman çıktısını biçimlendirmek için tostring'i çağırır.). Ancak, herhangi bir değeri biçimlendirirken tostring önce değerin  __tostring metametodunun olup olmadığını denetler.
Bu durumda, tostring  işi yapmak için bir argüman olarak nesnenin geçildiği metametodu çağırır.
Bu metametot 'un döndürdüğü şey tostring'in sonucudur.

Kümeler örneğimizde, kümeyi bir string olarak sunmak için hazırda bir fonksiyon tanımladık. Bu nedenle, metatablo'da sadece __tostring alanını ayarlamaya ihtiyaç var:

mt.__tostring = Set.tostring

Bundan sonra, argüman olarak kümeyle print'i çağırdığımızda, print , Set.tostring'i çağıran tostring'i çağırır:

s1 = Set.new {10, 4, 5}
print(s1) --> {4, 5, 10}

setmetatable ve getmetatable fonksiyonları ayrıca bir metaalan kullanır ,  bu durumda metatabloları korumak için. Kümelerinizi korumak istediğinizi varsayalım, bu şekilde kullanıcılar metatabloları ne görebilir ne de değiştirebilir. metatabloya __metatable alanı set ederseniz, getmetatable bu alanın değerini döndürür, buna karşılık setmetatable bir hata yükseltir:

mt.__metatable = "senin işin değil"

s1 = Set.new{}
print(getmetatable(s1)) --> senin işin değil
setmetatable(s1, {})
    stdin:1: cannot change protected metatable

13.4 Tablo-Erişim Metametotları

Aritmetik ve ilişkisel operatörler için olan metametotlar farklı durumlar için davranışlar tanımlar. Dilin normal davranışını değiştirmezler. Ancak Lua, iki normal durum için tabloların davranışını değiştirmek için bir yol sunar, bir tabloda bulunmayan alanları sorgu ve modifikasyonu.

__index metametodu

Daha önce, bir tabloda eksik bir alana eriştiğimizde sonucun nil olduğunu söyledim. Bu doğru, ama tamamen gerçek değil. Aslında, bu tür erişimler __index metametodunu aramasını için yorumlayıcıyı tetikler: böyle bir metot yoksa, genelde olduğu gibi, erişim nil olarak sonuçlanır; aksi takdirde metametot sonucu sağlar.

Burada klasik örnek kalıtımdır. pencerelerimi-kontrollerimizi betimleyen çeşitli tablolar oluşturmak istediğimizi varsayalım. Her tablo, konum, boyut, renk ve benzeri çeşitli pencere-kontrol parametrelerini tanımlamalıdır. Tüm bu parametrelerin varsayılan değerleri vardır ve bu şekilde sadece varsayılanı olmayan parametreleri vererek pencere-kontrol nesneleri yaratmak istiyoruz diyelim. İlk alternatif, olmayan alanları dolduran bir oluşturucu sağlamaktır. İkinci alternatif, yeni pencerelerin bir prototip penceresinden tüm eksik alanlar için miras almasını sağlamak. İlk olarak, bir metatabloyu paylaşan yeni pencereleri oluşturacak prototip ve  constructor-yapıcı fonksiyonu deklare ederiz:

Window = {} -- namespace-isim alanı oluştur
-- varsayılan değerli prototip oluştur
Window.prototype = {x = 0, y = 0, width = 100, height = 100}
Window.mt = {} -- create a metatable
-- constructor-yapıcı fonksiyonu deklare et
function Window.new(o)
    setmetatable(o, Window.mt)
    return o
end

Şimdi,  __index metametodunu tanımlıyoruz:

Window.mt.__index = function(table, key)
    return Window.prototype[key]
end

Bu koddan sonra, yeni bir pencere oluşturur ve sunulmamış alana sahip olup olmadığını sorguluyoruz:

w = Window.new {x = 10, y = 20}
print(w.width) --> 100

Lua, w'nin istenen alana sahip olmadığını ancak bir __index alanına sahip bir metatablo olduğunu algıladığında, w (tablo) ve “width” (eksik anahtar) argümanlarıyla bu __index metametodunu çağırır. Metametot, prototipi verilen anahtarla indeksler ve sonucu döndürür.

 miras için  __index metametodunun kullanımı,  çok yaygın bir Lua kısayolu sağlar . İsmine rağmen, __index metametodunun bir fonksiyon olması gerekmez: bunun yerine bir tablo olabilir. Bir fonksiyon olduğunda, Lua, daha yeni gördüğümüz üzere argümanları olarak olmayan anahtar ve tablo ile  onu çağırır. O bir tablo olduğunda, Lua bu tablodaki erişimi yineler. Bu şekilde önceki örneğimizde, __index'i basitçe şu şekilde deklare edebiliriz:

Window.mt.__index = Window.prototype

Artık Lua, metatablo'nun __index alanını baktığında bir tablo olan Window.prototip değerini bulur.
Sonuç olarak Lua bu tablodaki erişimi tekrarlar yani aşağıdaki kodun eşdeğerini yürütür:

Window.prototype["width"]

Bu erişim sonra istenen sonucu verir.

__index metametotu olarak bir tablonun kullanımı  tekli miras uygulamasının hızlı ve basit yolunu sağlar. fonksiyon, daha masraflı olmasına rağmen, daha fazla esneklik sağlar: çoklu miras, önbellekleme(caching) ve diğer birçok varyasyon uygulayabiliriz. Bu miras biçimlerini 16. bölümde ele alacağız.

Bir tabloya __index metametodunu çağırmadan erişmek istediğimizde rawget fonksiyonunu kullanırız. rawget(t, i) çağrısı, tablo t'e ham bir erişim yapar, yani metatabloları dikkate almadan ilkel-temel bir erişim. Ham erişim yapmak kodunuzu hızlandırmaz ( fonksiyon çağrısının yükü, sahip olabileceğiniz herhangi bir kazancı öldürür) ancak daha sonra göreceğimiz üzere bazen buna ihtiyacınız vardır.

__newindex metametodu

__newindex metametodu, tablo erişimleri için __index'in yaptığını, tablo güncellemeleri için yapar.
Bir tabloda olmayan bir indeks için bir değer atadığınızda, yorumlayıcı  __newindex metametodunu arar: varsa, yorumlayıcı atama yapmak yerine onu çağırır. __index 'deki gibi, metametot bir tablo ise, yorumlayıcı orjinali yerine bu tabloya atama yapar. Yine, metametodu es geçmenize izin veren bir raw fonksiyonu vardır: rawset(t,k, v) çağrısı herhangi bir metametodu çağırmadan tablo t'deki anahtar k ile ilişkilendirilmiş v değerini set eder.

 __index ve __newindex metametodlarının kombine kullanımı, salt okunur tablolar, varsayılan değerleri olan tablolar ve nesne yönelimli programlama için miras gibi Lua'da birkaç güçlü yapıları sağlar. Bu bölümde bu kullanımlardan bazılarını göreceğiz. Nesne yönelimli programlamanın kendi bölümü var.

Varsayılan değerli tablolar

Normal bir tablodaki herhangi bir alanın varsayılan değeri nil'dir. Bu varsayılan değeri metatablolar ile değiştirmek kolaydır:

function setDefault(t, d)
    local mt = {__index = function() return d end}
    setmetatable(t, mt)
end

tab = {x = 10, y = 20}
print(tab.x, tab.z) --> 10 nil
setDefault(tab, 0)
print(tab.x, tab.z) --> 10 0

setDefault çağrısından sonra tab'daki boş alana  herhangi bir erişim  sıfır döndüren (bu metametot için d'nin değeri) __index metametodunu çağırır.

setDefault fonksiyonu varsayılan değere ihtiyaç duyan her tablo için yeni bir metatablo oluşturur.
Varsayılan değerlere ihtiyaç duyan birçok tablomuz varsa bu masraflı olabilir. Bununla birlikte, metatablo, metametoduna bağlı d varsayılan değerine sahip bu nedenle fonksiyon tüm tablolar için tek bir metatablo kullanamaz. Farklı varsayılan değerlere sahip tablolar için tek bir metatablo kullanımına izin vermek için özel bir alan kullanarak tablonun kendisinde her tablonun varsayılan değerini saklayabiliriz. İsim çakışmaları konusunda endişelenmiyorsak, özel alanımız için “___” gibi bir anahtar kullanabiliriz:

local mt = {__index = function(t) return t.___ end}
function setDefault(t, d)
    t.___ = d
    setmetatable(t, mt)
end

İsim çakışmaları konusunda endişeleniyorsak bu özel anahtarın benzersizliğini sağlamak kolay.
Tek ihtiyacımız olan yeni bir tablo oluşturmak ve anahtar olarak onu kullanmaktır:

local key = {} -- benzersiz anahtar
local mt = {__index = function(t) return t[key] end}
function setDefault(t, d)
    t[key] = d
    setmetatable(t, mt)
end

Her tabloyu varsayılan değeriyle ilişkilendirmek için alternatif bir yaklaşım, indekslerin tablolar olduğu ve değerlerin varsayılan değerleri olduğu ayrı bir tablo kullanmaktır. Bununla birlikte, bu yaklaşımın doğru uygulanması için zayıf tablolar denilen özel bir tablo cinsine ihtiyacımız var
ve bu yüzden burada kullanmayacağız;  konuya 17. bölümde geri döneceğiz.

Başka bir alternatif, aynı varsayılanlarla tablolar için aynı metatabloyu yeniden kullanmak için metatabloların memoizasyonudur(Memoization). Bununla birlikte, bu da zayıf tablolara ihtiyaç duyuyor, böylece yine 17. bölüme kadar beklemek zorunda kalacağız.

Tablo Erişimlerini İzleme

Hem __index hem de __newindex yalnızca tabloda indeks bulunmadığında geçerlidir. Bir tabloya olan tüm erişimleri yakalamak için tek yol onu boş tutmaktır. Yani, bir tabloya tüm erişimleri izlemek istiyorsak, gerçek tablo için bir vekil(proxy) oluşturmalıyız. Bu proxy, tüm erişimleri izleyen ve bunları orijinal tabloya yönlendiren __index ve __newindex metametotlu boş bir tablodur. t'i izlemek istediğimiz orijinal tablo olduğunu varsayalım. Şöyle bir şey yazabiliriz:

t = {} -- orjinal tablo (herhangi yerde yaratıldı)

-- orijinal tabloya özel erişim sağlama
local _t = t

-- proxy yarat
t = {}

-- metatablo yarat
local mt = {
    __index = function(t, k)
        print("*access to element " .. tostring(k))
        return _t[k] -- orjinal tabloya eriş
    end,

    __newindex = function(t, k, v)
        print("*update of element " .. tostring(k) .. " to " .. tostring(v))
        _t[k] = v -- orjinal tabloyu güncelle
    end
}

setmetatable(t, mt)

Bu kod, t'e olan tüm erişimleri izler:

> t[2] = "hello"
*update of element 2 to hello
> print(t[2])
*access to element 2
hello

(Maalesef, bu düzenin tabloları dolaşmamıza imkan vermediğine dikkat edin. pairs fonksiyonu proxy üzerinde çalışacaktır,  orijinal tablo değil.)

Birkaç tabloyu izlemek istersek her biri için farklı bir metatabloya gerek yok. Bunun yerine, her bir proxy'i bir şekilde orjinal tablosuna ilişkilendirebilir ve tüm proxy'ler için ortak bir metatablo paylaşabiliriz. Bu sorun, önceki bölümde tartıştığımız varsayılan değerlerine tabloları ilişkilendirme sorununa benzer. Örneğin, özel bir anahtar kullanarak orijinal tabloyu bir proxy alanında tutabiliriz.
Sonuç aşağıdaki koddur:

local index = {} -- özel indeks yarat

local mt = {    -- metatablo yarat
    __index = function(t, k)
        print("*access to element " .. tostring(k))
        return t[index][k] --  orjinal tabloya eriş
    end,

    __newindex = function(t, k, v)
        print("*update of element " .. tostring(k) .. " to " .. tostring(v))
        t[index][k] = v -- orjinal tabloyu güncelle
    end
}

function track(t)
    local proxy = {}
    proxy[index] = t
    setmetatable(proxy, mt)
    return proxy
end

Artık tablo t'i izlemek istediğimizde tek yapmamız gereken t=track(t) 'i çalıştırmaktır.

Salt okunur tablolar

Salt okunur tabloları uygulamak için vekil-proxy konseptini adapte etmek kolay. Tek yapmamız gereken, tabloyu güncelleme girişimini takip ederken bir hata yükseltmek.  __index metametodu için, , fonksiyon yerine,  tablo kullanabiliriz;orijinal tablonun kendisini. sorguları izlemenize gerek yok; tüm sorguları orjinal tabloya yönlendirmek daha basit ve daha etkili. Bununla birlikte, bu kullanım, her bir salt okunur proxy için yeni bir metatablo gerektirir, __index orjinal tabloyu gösterecek şekilde:

function readOnly(t)
    local proxy = {}
    local mt = {        -- metatablo oluştur
        __index = t,
        __newindex = function(t, k, v)
            error("salt-okunur tabloyu güncelleme girişimi", 2)
        end
    }

    setmetatable(proxy, mt)
    return proxy
end

Kullanım örneği olarak, haftanın günleri için salt okunur bir tablo oluşturabiliriz:

gunler = saltOkunur{"Pazar", "Pazartesi", "Salı", "Çarşamba",
                  "Perşembe", "Cuma", "Cumartesi"}

print(gunler[1]) --> Pazar
gunler[2] = "Herhangi"
stdin:1: salt-okunur tabloyu güncelleme girişimi

Hiç yorum yok:

Yorum Gönder