14 Ortam

14 Ortam

Lua tüm global değişkenlerini ortam(environment) olarak adlandırılan normal bildik bir tabloda tutar. (Daha doğrusu Lua “global” değişkenlerini çeşitli ortamlarda tutar ancak bir süreliğine bu çokluğu görmezden geleceğiz.) Bu yapının bir avantajı Lua'nın içsel uygulamasını basitleştirmesidir çünkü global değişkenler için farklı bir veri yapısına ihtiyaç yoktur. Diğer (aslında ana) avantaj bu tabloyu diğer herhangi bir tablo gibi manipüle edebilmemizdir. Bu tür manipülasyonları kolaylaştırmak için Lua ortamın kendisini _G global değişkeninde tutar. (Evet, _G._G , _G'e eşdeğerdir.) Örneğin devamdaki kod mevcut ortamda tanımlı tüm global değişkenlerin adlarını yazdırır:

for n in pairs(_G) do print(n) end

Bu bölümde ortamı manipüle etmek için birkaç yararlı teknik göreceğiz.

14.1 Dinamik İsimli Global Değişkenler

Tipik olarak atama, global değişkenlere erişim ve ayarlama için yeterlidir. Bununla birlikte sıklıkla, bir şekilde çalışma zamanında hesaplanan veya  adı başka bir değişkende depolanan global bir değişkeni manipüle etmemiz gereken bir tür meta programlamaya ihtiyaç duyarız. Bu değişkenin değerini elde etmek için pek çok programcı şöyle bir şeyler yazmaya yönelir:

deger = loadstring("return " .. degiskenismi)()

Örneğin eğer degiskenismi x ise birleştirme işlemi çalıştırıldığında istenen sonucu elde edecek “return x” ile sonuçlanacaktır. Ancak bu kod yeni bir öbek oluşturulmasını ve derlenmesini içerir. Aynı etkiyi öncekinden daha verimli olan aşağıdaki kodla gerçekleştirebilirsiniz:


deger = _G[degiskenismi]


ortam, normal bildik bir tablo olduğundan istediğiniz anahtarla(değişken ismi) onu indeksleyebilirsiniz.

Benzer şekilde, adı dinamik olarak hesaplanan  global bir değişkene _G[degiskenismi]=değer yazarak değer atayabilirsiniz. Ancak dikkat edin: bazı programcılar bu imkanda biraz heyecanlanır ve a=var1 yazmanın kompleks yolu olan _G["a"]=_G["var1"] gibi bir kod yazarlar.

Önceki sorunun genelleşmesi, “io.read” veya “a.b.c.d” gibi dinamik adlı alanlara yol vermesi. Eğer _G["io.read"] yazarsak io tablosundan read alanını alamayız. Ancak getfield("io.read") yazarak beklenen sonucu döndürecek getfield fonksiyonunu kullanabiliriz. Bu fonksiyon esas olarak _G ile başlayıp alan alan ilerleyen bir döngüdür:

function getfield(f)
    local v = _G -- globaller tablosuyla başla
    for w in string.gmatch(f, "[%w_]+") do
        v = v[w]
    end
    return v
end

string kütüphanesinden gmatch, f 'deki tüm kelimeleri dolaşmak için kullanılır (burada “kelime” bir veya daha fazla alfanümerik karakter ve alt çizgi dizisidir).

Alanları değer set etmek için olan ilgili fonksiyon biraz daha karmaşık. a.b.c.d = v gibi bir atama aşağıdaki kodla eşdeğerdir:

local temp = a.b.c.
temp.d = v

Yani, son ada kadar alıp sonra ayrı ayrı işlemeliyiz. Devamdaki setfield fonksiyonu bu işi yapar aynı zamanda var olmadıklarında bir düzende ara tablolar oluşturur:

function setfield(f, v)
    local t = _G -- globaller tablosuyla başla
    for w, d in string.gmatch(f, "([%w_]+)(.?)") do
        if d == "." then -- son alan değil mi?
            t[w] = t[w] or {} -- yoksa tablo oluştur
            t = t[w] -- tabloyu al
        else -- son alan
            t[w] = v -- atama yap
        end
    end
end

Bu yeni şablon, w değişkeninde alan adını ve d değişkeninde isteğe bağlı devamındaki noktayı yakalar. (Bölüm 20'de şablon eşleştirmesini epey uzun ele alacağız.) Bir alan adını nokta takip etmezse o zaman o son ad.

önceki fonksiyonlar bağlamında şu çağrı

setfield("t.x.y", 10) 

global t tablosu, başka bir tablo t.x oluşturur ve  t.x.y 'e 10 atar:

print(t.x.y) --> 10
print(getfield("t.x.y")) --> 10

14.2 Global Değişken Deklarasyonu

Lua'da global değişkenlerin bildirimi gerekmez. Bu küçük programlar için kullanışlı olmasına rağmen, daha büyük programlarda bulunması zor basit yazım hatalarına neden olabilir. Ancak, isterseniz bu davranışı değiştirebiliriz. Lua global değişkenlerini klasik bir tabloda tuttuğu için global değişkenlere erişirken davranışını değiştirmek için metatabloları kullanabiliriz.

İlk yaklaşım basitçe global tablodaki tüm anahtarlara erişimi tespit eder:

setmetatable(
    _G,
    {
        __newindex = function(_, n)
            error("deklare edilmemiş değişkene yazma girişimi " .. n, 2)
        end,
        __index = function(_, n)
            error("deklare edilmemiş değişkeni okuma girişimi " .. n, 2)
        end
    }
)

Bu koddan sonra, var olmayan bir global değişkene erişim girişimi bir hatayı tetikleyecektir:

> print(a)
stdin:1: deklare edilmemiş değişkeni okuma girişim a

Peki, yeni değişkenleri nasıl deklare ederiz? Seçeneklerden biri metametotu bypass eden rawset kullanmak:

function declare(name, initval)
    rawset(_G, name, initval or false)
end

(false ile or, yeni global'in her zaman nil'den farklı bir değer almasını sağlar.)  Daha basit yol, ana öbekte global değişkenlere atamalara izin vermektir, böylece değişkenleri şöyle bildiririz:

a = 1

Atamanın ana öbekte olup olmadığını kontrol etmek için debug kütüphanesi kullanabiliriz. debug.getinfo (2,"S") çağrısı, metametotu çağrılan fonksiyonunun ana öbek mi, normal bir Lua fonksiyonu mu veya C fonksiyonu mu olup olmadığını belirten what alanı sahip bir tablo döndürür.(Bölüm 23'te debug.getinfo'u daha ayrıntılı olarak göreceğiz) Bu fonksiyonu kullanarak, devamdaki gibi __newindex metamethod'unu şöyle yeniden yazabiliriz:

__newindex = function(t, n, v)
    local w = debug.getinfo(2, "S").what
    if w ~= "main" and w ~= "C" then
        error("deklare edilmemiş değişkene yazma girişimi " .. n, 2)
    end
    rawset(t, n, v)
end

Bu yeni sürüm aynı zamanda C kodundan atamaları kabul eder, çünkü bu tür bir kod genellikle ne yaptığını bilir.

Bir değişkenin var olup olmadığını test etmek için, onu nil ile karşılaştıramayız, çünkü nil ise, erişim bir hata fırlatır. Bunun yerine, metamethod'u bypass eden rawget'ı kullanıyoruz:

if rawget(_G, var) == nil then
   -- ’var’ deklare edilmemiş
   ...
end

Aynen, düzenimiz, otomatik bildirilmemiş olarak dikkate alınacakları şeklinde, nil değerlere sahip global değişkenlere izin vermez. Ancak bu sorunu düzeltmek zor değil. Tek ihtiyacımız olan, bildirilen değişkenlerin adlarını tutan yardımcı bir tablodur. Bir metametot çağrıldığında bu tabloda değişkenin bildirilip bildirilmediğini denetler. Kod Listelesi 14.1'deki gibi olabilir. Şimdi x=nil gibi bir atama bile global bir değişken bildirmek için yeterlidir.

Her iki çözüm için de maliyet göz ardı edilebilir. İlk çözümle, metametotlar normal işler sırasında asla çağrılmaz. İkincisinde çağrılabilirler ancak yalnızca program bir nil tutan bir değişkene eriştiğinde.

Lua dağıtımı, esasen şimdi gözden geçirdiğimiz kodu kullanan global değişken kontrolu uygulayan strict.lua modülü ile birlikte gelir. Lua kodu geliştirirken onu kullanmak güzel bir alışkanlık.


14.3 Global Olmayan Ortamlar

Ortam ile ilgili sorunlardan biri global olmasıdır. Üzerinde yaptığınız herhangi bir değişiklik, programınızın tüm bölümlerini etkiler. Örneğin global erişim denetimi için bir metatablo yüklediğinizde tüm programınız ilkelere uymak zorunda. Bildirmeden, global değişkenleri kullanan bir kütüphane kullanmak istiyorsanız kötü şanslısınız.

Liste 14.1. global-değişken bildiriminin kontrolu:

local declaredNames = {}

setmetatable(_G, {
        __newindex = function(t, n, v)
            if not declaredNames[n] then
                local w = debug.getinfo(2, "S").what
                if w ~= "main" and w ~= "C" then
                    error("attempt to write to undeclared variable " .. n, 2)
                end
                declaredNames[n] = true
            end
            rawset(t, n, v) -- fiili atama yap
        end,

        __index = function(_, n)
            if not declaredNames[n] then
                error("attempt to read undeclared variable " .. n, 2)
            else
                return nil
            end
        end
})

Lua 5, her fonksiyonun kendi ortamına sahip olmasına imkan vererek bu sorunu global değişkenler için iyileştirdiği görünüyor; Bu imkan ilk başta garip gelebilir; sonuçta, global değişkenlerin tablosunun amacı global olmaktır. Bununla birlikte, Bölüm 15.3'te bu imkanın global değerlerin her yerde hala mevcut olabildiği birkaç ilginç yapıya izin verdiğini göreceğiz.

Bir fonksiyonun ortamını setfenv fonksiyonu (set function environment kısaltması) ile değiştirebilirsiniz. argüman olarak fonksiyon ve yeni ortamı alır. fonksiyonun kendisi yerine,
verilen yığın düzeyinde etkin fonksiyon anlamına gelen bir sayı da verebilirsiniz. 1 sayısı , mevcut fonksiyon anlamına gelir, 2 sayısı, geçerli fonksiyonu çağıran fonksiyon anlamına gelir (çağıranın ortamını değiştiren yardımcı fonksiyonlar yazmak için kullanışlıdır) vb.

setfenv kullanımında ilk pür girişim başarısız. Kod:

a = 1 -- global değişken yarat
-- mevcut ortamı yeni boş tabloya çevir
setfenv(1, {})
print(a)

sonuç:

stdin:5: attempt to call global ’print’ (a nil value)

(Bu kodu tek öbekte çalıştırmalısınız. Etkileşimli modda satır satır girerseniz, her satır farklı bir fonksiyondur ve setfenv çağrısı yalnızca kendi satırını etkiler.) ortamınızı değiştirdikten sonra,
tüm global erişimler yeni tabloyu kullanır. Boşsa, tüm global değişkenlerinizi, hatta _G'i kaybedersiniz. Yani, önce eski ortam gibi bazı yararlı değerlerle onu çoğaltmalısınız:

a = 1               -- global değişken yarat
setfenv(1, {g = _G})-- mevcut ortamı değiştir
g.print(a)          --> nil
g.print(g.a)        --> 1

Şimdi, “global” g'e eriştiğinizde, değeri eski ortam, burada print alanını bulacaksınız.

g yerine _G adını kullanarak önceki örneği yeniden yazabiliriz:

setfenv(1, {_G = _G})
_G.print(a)     --> nil
_G.print(_G.a)  --> 1

Lua için, _G diğerleri gibi bir isim. Tek özel durumu Lua ilk global tabloyu oluşturduğu ve bu tabloyu _G global değişkenine atadığı zaman meydana gelir. Lua'nın bu değişkenin geçerli değeri umurunda değil; setfenv yeni ortamlarda onu set etmez. Ancak, yeniden yazılan örnekte yaptığımız gibi, ilk global tabloya bir referansımız olduğunda bu aynı adı kullanmak gelenekseldir.

Yeni ortamınızı çoğaltmak için başka bir yol miras ile yapmaktır:

a = 1
local newgt = {}    -- yeni ortam yarat
setmetatable(newgt, {__index = _G})
setfenv(1, newgt)   -- set et
print(a)            --> 1

Bu kodda, yeni ortam hem print hem de a'yı eskisinden alır. Bununla birlikte, herhangi bir atama yeni tabloya gider. Gerçekten global bir değişkeni yanlışlıkla değiştirme tehlikesi yoktur, ancak bunları _G ile hala değiştirebilirsiniz:

-- önceki kod devamı
a = 10
print(a)    --> 10
print(_G.a) --> 1
_G.a = 20
print(_G.a) --> 20

Her fonksiyon veya daha spesifik olarak her closure, bağımsız bir ortama sahiptir. Bir sonraki öbek bu mekanizmayı göstermektedir:

function factory()
    return function()
        return a -- "global" a
    end
end

a = 3

f1 = factory()
f2 = factory()
print(f1()) --> 3
print(f2()) --> 3

setfenv(f1, {a = 10})
print(f1()) --> 10
print(f2()) --> 3

factory fonksiyonu, global a değerlerini döndüren basit closurelar oluşturur. factory'e her çağrı kendi ortamı ile yeni bir closure oluşturur. Yeni bir fonksiyon oluşturduğunuzda, onu yaratan fonksiyondan ortamını miras alır. Bu şekilde oluşturulduğunda bu closure'lar a değerinin 3 olduğu global ortamı paylaşır. setfenv(f1,{a=10}) çağrısı, f1 ortamını, f2 ortamını etkilemeden, a değerinin 10 olduğu yeni bir ortama değiştirir.

Yeni fonksiyonlar, ortamlarını onları oluşturan fonksiyondan devraldığından, bir öbek kendi ortamını değiştirir ise daha sonra tanımladığı tüm fonksiyonlar bu yeni ortamı paylaşacaktır. Bu, bir sonraki bölümde göreceğimiz gibi isimalanları oluşturmak için yararlı bir mekanizmadır.

Hiç yorum yok:

Yorum Gönder