osa1 github gitlab twitter cv rss

Lua - basitlik ve esneklik hakkında birkaç şey

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 yyi property decoratorü içerisinde methodlar olarak tanımlamak3 Lua’da bunu şöyle yaptım:

Rect = { centerx = 0,
         centery = 0,
         angle = 0,
         width = 0,
         height = 0 }
function Rect_indexfn (table, key)
    local f, o
    if key == 'x' then
        f = math.cos
        o = table.centerx
    elseif key == 'y' then
        f = math.sin
        o = table.centery
    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
a = Rect.new({centerx = 0, centery = 0, angle = 30, width = 6, height = 8})
b = Rect.new({centerx = 0, centery = 0, angle = 30, width = 6, height = 8})
b.angle = 60
print(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 = v[key]
            if v then
                return v
            end
        end
    end
    setmetatable(self, { __index = indexfn })
    return self
end
-- bu ikisi superclass gorevinde
st1 = { attr1 = "st1 attr1" }
st2 = { attr1 = "st2 attr1", attr2 = "st2 attr2" }
a = Rect.new({centerx = 0, centery = 0, angle = 30, width = 6, height = 8}, { st1, st2 })
b = Rect.new({centerx = 0, centery = 0, angle = 30, width = 6, height = 8}, { st2 })
b.angle = 60
print(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_indexfnyi ç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 :) .


  1. İlgili makaleler: Implementation of Lua 5.0, Lua - an extensible extension language, The Evolution of Lua.↩︎

  2. 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.↩︎

  3. 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.↩︎