May 7, 2012 - Tagged as: lua, tr.
Programlama dillerini incelediğimi buraları okuyanlar farketmiştir herhalde :) . Bir süredir Lua ile alakalı birşeyler okuyordum. Beni epey etkiledi. İlk yorumlayıcısı ve performansı etkilemişti1 Daha sonradan kaynağını kurcalamış ve ne kadar küçük ve düzenli olduğunu görüp şaşırmıştım. Dilin desteklediği Tail-call optimization ve lexical scope’un(ve dolayısıyla closureların) zaten hastasyım. Sonralardan SO gibi ortamlarda “mühendislik harikası” şeklinde nitelendirildiğini gördüm. Henüz bu kısımları değerlendirebilecek seviyede değilim. Ben dilin sadeliği ve bu sadeliğe rağmen nasıl kolayca esneklik sağlayabildiği hakkında birşeyler yazacağım.
Dildeki tek veri yapısı tablolar. Arrayler, mapler, modüller ve dil ile gelen tüm yapılar direkt olarak tablo. Bir veri yapısı yazmak istediğiniz de de tabloları kullanmak zorundasınız. Nasıl Python’da herşey nesneyse, Lua’da da neredeyse herşey tablo2 Sınıflarla sağlanan bir nesne tabanlı programlama desteği yok. Ama çok basit ve güzel bir özellik sayesinde, multiple inheritance bile sağlayabiliyorsunuz.
Tüm tablolara metatable
denilen bir tablo atayabiliyorsunuz. Bu metatablolar(ne diyeyim bilemedim bunlara) tablolar operatörlerle işleme sokulduğunda çağırılacak fonksiyonları tutuyorlar. Örneğin bir tablonun metatablosu __add
gibi bir fonksiyona sahipse, bu tabloyu toplama işlemine sokabiliyoruz. Aynı Python’daki __add__
methodu gibi. Eğer tabloda olmayan bir değer erişmek istersek de, benzer bir şekilde metatablodaki __index
fonksiyonuna, eğer tabloda olmayan bir değer eklemeye çalışırsak(mevcut bir değeri değiştirme değil de farklı anahtara sahip bir değer ekleme yani) da __newindex
fonksiyonu çağırılıyor. Bunlar aynı Python’daki __getattr__
ve __setattr__
gibiler.
Bugün yazdığım uygulamada şöyle bir sorun yaşadım. Rect adlı bir tablo tutuyorum ve bu tabloyu metatablo olarak kullanan başka tablolar oluşturuyorum. Sanki Rect diye bir sınıfım varmış da nesneler oluşturuyormuşum gibi. Bir yerden sonra bu tablonun implementasyonunda ciddi bir değişiklik yapıyorum ve x
ve y
özellikleri yerine artık bir center
ve angle
diye iki özellik ekliyorum. Ama bu tablonun bazı x
ve y
özelliklerini kullanan fonksiyonlarımı teker teker değiştirmemem gerek. Python konuşanlara bunu şöyle özetleyebiliriz, sınıfımdan iki özellik siliyorum, bunların artık sınıftaki diğer özelliklerden hesaplanması gerek. Ama kodumu refactor etmek istemiyorum. Bu durumda Python’da yapılacak işlem bariz: x
ve y
yi property
decoratorü içerisinde methodlar olarak tanımlamak3 Lua’da bunu şöyle yaptım:
= { centerx = 0,
Rect = 0,
centery = 0,
angle = 0,
width = 0 }
height function Rect_indexfn (table, key)
local f, o
if key == 'x' then
= math.cos
f = table.centerx
o elseif key == 'y' then
= math.sin
f = table.centery
o end
if f then
return o-math.abs(f(math.rad(table.angle)))*math.pow(math.pow(table.height, 2)+math.pow(table.width, 2), 0.5)/2
end
end
function Rect:new()
setmetatable(self, { __index = Rect_indexfn })
return self
end
= Rect.new({centerx = 0, centery = 0, angle = 30, width = 6, height = 8})
a = Rect.new({centerx = 0, centery = 0, angle = 30, width = 6, height = 8})
b .angle = 60
bprint(a.x) -- -4.3301270189222
print(b.y) -- -4.3301270189222
Tek yaptığım, yeni bir Rect oluşturuğum tablonun metatablosunu daha önceden belirlediğim Rect_indexfn
fonksiyonunun __index
fonksiyonu olarak tutan bir tablo olarak belirlemek. Bundan ne zaman kendisinin sahip olmadığı bir değere erişilmeye çalışsa, bu fonksiyon çalışarak erişilmek istenen değere göre işlemler yapıyor. Bu arada :
ile tanımladığım fonksiyonların aldığı ilk parametre self
değerine atanıyor. Rect:new()
fonksiyonum hiçbir parametre almıyormuş gibi gözüksede, self
adlı bir parametre alıyor yani aslında.
Şimdi normalde bunu gösterip bitirecektim ama multiple inheritance implementasyonu da o kadar kolay ki atlayamadım:
function Rect:new(ps)
-- lua'da vararglar biraz sorunlu oldugundan superclasslari
-- bir array olarak alacagim. detaylar icin: http://lua-users.org/wiki/VarargTheSecondClassCitizen
local function indexfn (table, key)
local v = Rect_indexfn(table, key)
if v then
return v
end
for i,v in ipairs(ps) do
= v[key]
v if v then
return v
end
end
end
setmetatable(self, { __index = indexfn })
return self
end
-- bu ikisi superclass gorevinde
= { attr1 = "st1 attr1" }
st1 = { attr1 = "st2 attr1", attr2 = "st2 attr2" }
st2 = Rect.new({centerx = 0, centery = 0, angle = 30, width = 6, height = 8}, { st1, st2 })
a = Rect.new({centerx = 0, centery = 0, angle = 30, width = 6, height = 8}, { st2 })
b .angle = 60
bprint(a.x) -- -4.3301270189222
print(b.y) -- -4.3301270189222
print(a.attr1) -- st1 attr1
print(a.attr2) -- st2 attr2
print(b.attr1) -- st2 attr1
Constructoru bu şekilde değiştirdim. __index
fonksiyonunun yeni haline bakarsanız, ilk başta Rect_indexfn
yi çağırıyor, eğer hala gerekli değerler bulunamıyorsa, soldan itibaren superclassları aramaya başlıyor. Burda ben en basit implementasyonu yapmaya çalıştım, eğer superclasslar da bu şekilde tanımlanmış metatabloya sahiplerse, arama işlemi ilk superclassın superclasslarıyla devam edecek. Kolayca farklı davranışlar tanımlanabilir, tüm algoritma indexfn
fonksiyonundan ibaret. Hayal gücünüzce geliştirip, diamond problem ile karşılaşıp, kendi çözümünüzü uydurabilirsiniz :) .
İlgili makaleler: Implementation of Lua 5.0, Lua - an extensible extension language, The Evolution of Lua.↩︎
Neredeyse. Sayılar(number
) değil. Başka tablo olmayan var mı şu anda aklıma gelmiyor. Tablo implementasyon detayları için “Implementation of Lua 5.0” yazısına bakabilirsiniz.↩︎
Java’cılar zaten tüm özellikleri private yapıp, bir refleks olarak getter/setterlar tanımladıkları için onlar için sorun yok ehuaehe.↩︎