6 Fonksiyonlar hakkında daha fazlası

6 Fonksiyonlar hakkında daha fazlası

Lua 'da fonksiyonlar özel sözcüksel kapsamlı(lexical scope) birinci sınıf değerdirler.

Fonksiyonların "birinci sınıf değer" olması ne anlama geliyor? Bunun anlamı, Lua'da bir fonksiyon, string , sayı gibi klasik değerlerle aynı statüye sahip bir değerdir . Fonksiyonlar, tablolar ve değişkenlerde (hem global hem de yerel) saklanabilir, başka fonksiyonlara argüman olarak geçilebilir ve bunlardan döndürülebilirler.

"sözcüksel kapsam" ne anlama geliyor? Bunun anlamı, Fonksiyonlar kendini kapsayan fonksiyonların değişkenlerine erişebilir(haliyle bunun anlamı Lua aynı zamanda Lamda kalkülüs modelini içerir.). Bu bölümde göreceğimiz üzere bu özellik dile büyük güç getiriyor çünkü Lua fonksiyonel dil dünyasından birçok güçlü programlama tekniğine başvurmamıza imkan sağlıyor. Fonksiyonel programlamaya hiç ilginiz olmasa bile programlarınızı daha küçük ve daha basit hale getirebildikleri için bu teknikleri keşfedişimizde biraz bilgi edinmeye değer.

Lua'da fonksiyonlar ile ilgili biraz kafa karıştırıcı olay diğer tüm değerler gibi anonim olmalarıdır; isimleri yoktur. print adında bir fonksiyon dediğimizde aslında bu fonksiyonu tutan bir değişken hakkında konuşuyoruz. Herhangi başka bir değeri tutan herhangi başka bir değişken gibi bu tür değişkenleri birçok yönden manipüle edebiliriz. Aşağıdaki örnek biraz aptalca olsa da olayı gösteriyor:

a = {p = print}
a.p("Merhaba Dünya")              --> Merhaba Dünya
print = math.sin                         -- 'print' artık sinüs fonksiyonu
a.p(print(1))                              --> 0.841470
sin = a.p                                    -- 'sin' şimdi print fonksiyonu
sin(10, 20)                                --> 10         20

(Daha sonra bu imkan için daha yararlı uygulamalar göreceğiz.)

Fonksiyonlar değer ise fonksiyonları oluşturan ifadeler var mı? Evet. Aslında Lua'da bir fonksiyon yazmak için olağan yöntem bu, şöyle ki

function foo (x) return 2*x end

Bu , küpşeker sözdizim(syntactic sugar) dediğimiz şeyin bir örneğidir sadece; başka bir deyişle devamdaki kodu yazmanın rafineri bir yoludur:

foo = function (x) return 2*x end


Yani bu durumda bir fonksiyon tanımlaması bir deyimdir(atamadır , daha spesifik olarak) “function" tipinde bir değer oluşturulur ve bir değişkene atanır. "function (x) gövde end" ifadesini bir tablo oluşturucu{} gibi olan bir fonksiyon oluşturucusu olarak görebiliriz. Bu tür fonksiyon oluşturucularının ürettiklerine anonim fonksiyon olarak adlandırıyoruz. Genellikle fonksiyonları isimleyip global değişkenlere atasak da fonksiyonların anonim kaldığı çeşitli durumlar vardır. Bazı örnekler görelim.

Tablo kütüphanesi table.sort fonksiyonunu sağlar, bir tablo alır ve öğelerini sıralar. Böyle bir fonksiyon  sıralama düzeninde sınırsız varyasyona imkan vermelidir: artan veya azalan, sayısal veya alfabetik, anahtarla sıralanmış tablolar vb. Her türlü seçeneği sağlamaya çalışmak yerine  sort, sadece tek bir opsiyonel parametre olarak bir sıralama fonksiyonu sağlar : ki bu fonksiyon iki öğe alır ve seri listede ikincisinden önce ilkinin gelip gelmediğini döndürür. Örneğin devamdaki gibi kayıtlara sahip bir tablomuz olduğunu varsayalım:

network = {
{name = "grauna", IP = "210.26.30.34"},
{name = "arraial", IP = "210.26.30.23"},
{name = "lua", IP = "210.26.23.12"},
{name = "derain", IP = "210.26.23.20"},
}

Tabloyu name alanına göre sıralamak istiyorsak, ters alfabetik düzende, sadece şunu yazıyoruz:

table.sort(network, function (a,b) return (a.name > b.name) end)

Anonim fonksiyonun bu deyimde ne kadar kullanışlı olduğunu görüyoruz.

sort gibi, bir argüman olarak başka bir fonksiyonu alan fonksiyona yüksek düzeyli fonksiyon(high order function) olarak adlandırıyoruz. Yüksek düzeyli fonksiyonlar güçlü bir programlama mekanizmasıdır ve fonksiyon argümanlarını oluşturmak için anonim fonksiyonların kullanılması büyük bir esneklik kaynağıdır. Tabi yüksek düzeyli fonksiyonların özel haklara sahip olmadığını unutmayın; Lua'nın fonksiyonları birinci sınıf değerler olarak işleme yeteneğinin doğrudan bir sonucudurlar.

yüksek düzeyli fonksiyonların kullanımını daha da göstermek için yaygın bir yüksek düzey fonksiyonun, türevin, saf uygulamasını görelim. Resmi tanımda, x noktasındaki f fonksiyonun türevi d sonsuz küçük olduğunda (f(x + d) − f(x))/d 'in değeridir. Böylece türevi devamdaki gibi hesaplayabiliriz:

function turev(f, delta)
delta = delta or 1e-4
return function (x)
return (f(x + delta) - f(x))/delta
end
end


turev(f) çağrısı başka bir fonksiyon olan f fonksiyonunun türevini döndürür(yaklaşık olarak).


c = turev(math.sin)
print(math.cos(10), c(10))
--> -0.83907152907645              -0.83904432662041

Fonksiyonlar Lua'da birinci sınıf değerler olduğundan yalnızca global değişkenlerde değil aynı zamanda yerel değişkenlerde ve tablo alanlarında da depolayabiliriz. Daha sonra göreceğimiz üzere tablo alanlarında fonksiyonların kullanımı, modüller ve nesne yönelimli programlama gibi bazı gelişmiş Lua kullanımları için önemli bir bileşendir.

6.1 Closure'lar(Kapatmalar)

Bir fonksiyon başka bir fonksiyon içinde yazıldığında kapsayan fonksiyonun tüm yerel değişkenlerine tam erişime sahiptir; bu özellik sözcüksel kapsama olarak adlandırılır. Bu görünürlük kuralı kulağa hafif gelse de, değil. Sözcüksel kapsama artı birinci sınıf fonksiyonlar bir programlama dilinde güçlü bir kavramdır ancak birkaç dil bunu desteklemektedir.

Basit bir örnekle başlayalım. Öğrenci isimleri listesi ve isimlere ilişkilendirilen notlar tablosu olduğunu varsayalım; notlara göre isim listesini sıralamak istersiniz (önce daha yüksek notlar). Bu görevi aşağıdaki gibi yapabilirsiniz:

isimler = {"Peter", "Paul", "Mary"}
notlar = {Mary = 10, Paul = 7, Peter = 8}
table.sort(isimler, function (n1, n2)
return notlar[n1] > notlar[n2]                                -- notları karşılaştır
end)


Şimdi bu görevi yapmak için bir fonksiyon oluşturmak istediğinizi varsayalım:

function notlasirala(isimler, notlar)
table.sort(isimler, function (n1, n2)
return notlar[n1] > notlar[n2]                             -- notları karşılaştır
end)
end



Örnekteki ilginç nokta, sıralamak için verilen anonim fonksiyonun onu kapsayan notlasirala fonksiyonun yerel parametresi olan notlar'a erişmesidir. Bu anonim fonksiyon içinde ne global bir değişken var ne yerel bir değişken var, yerel olmayan değişken diyelim ona. (Tarihsel nedenlerden dolayı yerel olmayan değişkenlere Lua'da üst değerler(upvalues) denir.)

Bu nokta neden bu kadar ilginç? Çünkü fonksiyonlar birinci sınıf değerlerdir. Aşağıdaki kodu düşünün:

function newCounter ()
local i = 0
return function () -- anonim fonksiyon
i = i + 1
return i
end
end

c1 = newCounter()
print(c1())                 --> 1
print(c1())                 --> 2

Bu kodda anonim fonksiyon sayacını tutmak için yerel olmayan değişken i'den yararlanır. Ancak anonim fonksiyonu çağırdığımız zaman bu değişkeni oluşturan (newCounter) fonksiyonu return ettiği için i hazırda kapsam dışı. Lua bu durumu kapatmalar(closure) kavramını kullanarak uygun bir şekilde ele alır. Basitçe söylemek gerekirse bir kapatma , uygun şekilde yerel olmayan değişkenlere erişmek için gerekeni yapan artı bir fonksiyondur. newCounter'ı tekrar çağırırsak yeni bir yerel değişken olan i'i oluşturacak bu şekilde bu yeni değişken üzerinden hareket eden yeni bir kapatmamız olacak:

c2 = newCounter()
print(c2())              --> 1
print(c1())              --> 3
print(c2())              --> 2

Velhasıl, c1 ve c2, aynı fonksiyon üzerinde farklı kapatmalardır ve her biri  i yerel değişkenin bağımsız bir örneğinden hareket eder.

Teknik olarak konuşursak, Lua'da kapatma bir değerdir, fonksiyon değil. Fonksiyonun kendisi sadece kapatmalar için bir prototiptir. Yine de “fonksiyon” terimini kullanmaya devam edeceğiz herhangi bir kargaşaya mahal vermemek için.

Kapatmalar birçok bağlamda değerli imkan sağlar. Gördüğümüz gibi, sort gibi yüksek düzeyli fonksiyonlara argümanlar olarak kullanışlıdırlar. Kapatmalar, newCounter örneğimiz gibi diğer fonksiyonları oluşturan fonksiyonlar için de değerlidir; bu mekanizma, Lua programlarının fonksiyonel dünyadan sofistike programlama tekniklerine başvurma imkanı verir. Kapatmalar callback fonksiyonları için de yararlıdır. Burada tipik örnek, geleneksel bir GUI araç setinde düğmeler oluşturduğunuzda olur. Her düğmenin kullanıcı düğmeye bastığında çağrılacak callback fonksiyonu vardır;  basıldığında biraz farklı şeyler yapan farklı düğmeler istersiniz. Örneğin, bir dijital hesap makinesinin hepsi sayısal on benzer düğmeye ihtiyacı vardır. Her birini devamdaki gibi bir fonksiyonla oluşturabilirsiniz:

function digitButton (digit)
return Button{ label = tostring(digit),
action = function ()
add_to_display(digit)
end
}
end



Bu örnekte Button'un yeni düğmeler oluşturan bir kit fonksiyon olduğunu varsayıyoruz; label düğme etiketidir; ve action, düğme basıldığında çağrılacak kapatma callback'dır. callback, digitButton görevini yaptıktan sonra ve yerel değişken digit kapsamdan çıktıktan sonra  da çağrılabilir ama yine de digit değişkenine erişebilir.

Kapatmalar aynı zamanda oldukça farklı bir bağlamda da değerlidir. Fonksiyonlar nornal değişkenlerde depolandığı için Lua daki fonksiyonları kolaylıkla yeniden tanımlayabiliriz hatta ön tanımlı fonksiyonları bile. Bu imkan Lua'nın çok esnek olma sebeblerinden biridir. Sık sık, bir fonksiyonu yeniden tanımladığınızda yeni uygulamada orijinal fonksiyona ihtiyacınız olur. Örneğin, radyan yerine derecelerde çalışacak şekilde sin fonksiyonunu yeniden tanımlamak istediğinizi varsayalım. Bu yeni fonksiyon argümanını dönüştürmeli ve ardından gerçek işi yapacak orijinal sin fonksiyonunu çağırmalıdır. Kodunuz şöyle olabilir:

oldSin = math.sin
math.sin = function (x)
return oldSin(x*math.pi/180)
end



Bu yeniden tanımlamayı yapmanın daha temiz yolu aşağıdaki gibidir

do
local oldSin = math.sin
local k = math.pi/180
math.sin = function (x)
return oldSin(x*k)
end
end


Burada eski sürümü özel bir değişkende tutuyoruz; yeni sürümden eskisine erişmenin tek yolu o.

Aynı tekniği, sandbox olarak da adlandırılan güvenli ortamlar oluşturmak için kullanabilirsiniz. Bir sunucu ile internet üzerinden alınan kod gibi güvenilmeyen kodları çalıştırırken güvenli ortamlar gereklidir. Örneğin programın erişebileceği dosyaları kısıtlamak için io.open'ı kapatmalar kullanarak yeniden tanımlayabiliriz:

do
local oldOpen = io.open
local access_OK = function (filename, mode)
<erişimi kontrol et>
end
io.open = function (filename, mode)
if access_OK(filename, mode) then
return oldOpen(filename, mode)
else
return nil, "erişim rededildi"
end
    end
end


Bu örneği güzel yapan şey, bu yeniden tanımlamadan sonra programın yeni, kısıtlı sürüm dışında serbest olarak orjinal güvenliksiz open fonksiyonunu çağırmasının bir yolu yoktur. Güvenliksiz orjinal sürüm dışarıdan erişilemeyen bir kapatmada  özel bir değişken olarak tutulur. Bu teknikle her zamanki faydalarla, basitlik ve esneklik, Lua'da Lua sandbox'larını inşa edebilirsiniz. tekbeden-herkeze-uyar çözümü yerine Lua, size, özel güvenlik ihtiyaçlarınız için ortamınızı kesip-biçip-dikip uyarlayabilmeniz için bir meta-mekanizma sunar.

6.2 Global olmayan Fonksiyonlar

Birinci sınıf fonksiyonların bariz bir sonucu, fonksiyonları yalnızca global değişkenlerde değil
aynı zamanda tablo alanlarında ve yerel değişkenlerde de depolayabilmemizdir.

Tablo alanlarındaki fonksiyonların zaten birkaç örneği gördük: Lua kütüphanelerinin çoğu bu mekanizmayı kullanıyor (örneğin, io.read, math.sin). Lua'da bu tür fonksiyonlar oluşturmak için fonksiyonlar ve tablolar için uygun söz dizimini bir araya getirmeliyiz yalnızca:

Lib = {}
Lib.foo = function (x,y) return x + y end
Lib.goo = function (x,y) return x - y end


Elbette, kurucuları da kullanabiliriz:

Lib = {
foo = function (x,y) return x + y end,
goo = function (x,y) return x - y end
}

Dahası, Lua bu tür fonksiyonları tanımlamak için başka bir sözdizimi sunuyor:

Lib = {}
function Lib.foo (x,y) return x + y end
function Lib.goo (x,y) return x - y end


Bir fonksiyonu yerel bir değişkene depoladığımızda, yerel bir fonksiyon, yani belirli bir kapsamla sınırlı bir fonksiyon elde ederiz. Bu tür tanımlamalar paketler için özellikle yararlıdır: Lua her bir chunk'ı bir fonksiyon olarak işlediğinden, bir chunk, yalnızca chunk içinde görünür olan yerel fonksiyonları bildirebilir. sözcüksel kapsam, paketteki diğer fonksiyonların bu yerel fonksiyonları kullanabilmesini sağlar:

local f = function (<parametreler>)
<gövde>
end

local g = function (<parametreler>)
<bazı kodlar>
f()                    -- ’f’ burada görünür
<bazı kodlar>
end


Lua, yerel fonksiyonların bu tür kullanımlarını onlar için küpşeker sözdizim ile destek sağlar:

local function f (<parametreler>)
<gövde>
end

yinelemeli yerel fonksiyonların tanımlamasında ince bir nokta ortaya çıkmakta. saf yaklaşım burada çalışmaz:

local fact = function (n)
if n == 0 then return 1
else return n*fact(n-1)           -- problem
end
end


Lua, fonksiyon gövdesindeki fact(n-1) çağrısını derlediğinde yerel fact henüz tanımlı değil. Bu nedenle bu ifade yerel olanı değil global fact'i çağırır. Bu sorunu çözmek için önce yerel değişkeni tanımlamalı ve sonra fonksiyonu tanımlamalıyız:

local fact
fact = function (n)
if n == 0 then return 1
else return n*fact(n-1)
end
end

Artık fonksiyon içindeki fact yerel değişkene başvurabilir. fonksiyon tanımlandığındaki değeri önemli değil; fonksiyon çalıştırıldığında fact zaten doğru değere sahip olacak.

Lua, yerel fonksiyonlar için küpşeker sözdizimi genişlettiğinde saf tanımlamayı kullanmaz. Bunun yerine şunun gibi tanımlama

local function foo (<parametreler>) <gövde> end

devamdakine evrilir

local foo
foo = function (<parametreler>) <gövde> end


Böylece bu sözdizimini endişe etmeden yinelemeli fonksiyonlar için kullanabiliriz:

local function fact (n)
if n == 0 then return 1
else return n*fact(n-1)
end
end

Tabii ki de endirekt yinelemeli fonksiyonlarınız varsa bu hile çalışmaz. Bu gibi durumlarda  ileri deklarasyona denk gelen hileyi kullanmanız gerekir:

local f, g -- ’ileri’ deklarasyon

function g ()
<bazı kodlar>    f()     <bazı kodlar>
end

function f ()
<bazı kodlar>    g()     <bazı kodlar>
end



Son tanımlamada local function f yazılmadığına dikkat. Aksi takdirde Lua, orijinal f'yi (g ile bağlı olan) tanımsız bırakarak yeni bir yerel f değişkeni oluşturacak.

6.3 Özel Kuyruk Çağrılar

Lua'daki fonksiyonların diğer bir ilginç özelliği de Lua'nın kuyruk-çağrı çıkarsaması yapmasıdır.
(konsept direkt özyineleme değilsede özel Lua kuyruk özyinelemesidir.)

kuyruk çağrı çağrıya bürünmüş bir goto 'dır. Yapacak birşey kalmadan son eylem olarak diğer bir fonksiyonun çağrısı kuyruk çağrıdır. Örneğin aşağıdaki kodda g çağrısı bir kuyruk çağrıdır:

function f (x) return g(x) end

f'nin g'i çağırdıktan sonra yapacak başka bir şeyi yok. Bu gibi durumlarda programın çağrılan fonksiyon sona erdiğinde çağıran fonksiyonuna geri dönmesi gerekmez. Bu nedenle kuyruk çağrısından sonra programın stack'da çağıran fonksiyon hakkında herhangi bir bilgi tutmasına ihtiyacı yoktur. g döndüğünde kontrol direkt f'nin çağrıldığı noktaya döndürülebilir. Lua gibi bazı dil uygulamaları bu gerçekten yararlanır ve gerçekte bir kuyruk çağrısı yapılırken herhangi bir ekstra stack alanı kullanılmaz. Buna uygulamaların kuyruk-çağrı çıkarsaması yapması diyoruz.

Kuyruk çağrıları stack alanı kullanmadığından bir programın yapabileceği iç içe kuyruk çağrıları sayısı üzerinde bir sınır yoktur. Örneğin argüman olarak herhangi bir sayı geçilebilen aşağıdaki fonksiyonu çağırabiliriz; stack asla taşmayacaktır:

function foo (n)
if n > 0 then return foo(n - 1) end
end

bir kuyruk çağrısı için kuyruk çağrı çıkarsamasını varsaymak ince nokta. çağrıdan sonra çağıran fonksiyonun yapacak bir şeyi kalmamalı kriterinin yitirilmesine aday durumlar var. Örneğin aşağıdaki kodda g çağrısı bir kuyruk çağrısı değildir:

function f (x) g(x) end

Bu örnekte sorun, g çağrısından sonra f, zaman zaman g sonuçlarını geri dönmeden önce atmak zorunda kalır. Benzer şekilde aşağıdaki tüm çağrılarda kriter bozulur:

return g(x) + 1                     -- toplama yapmalı
return x or g(x)                    -- bir sonuca varılmalı
return (g(x))                         -- bir sonuca varılmalı

Lua'da yalnızca "return fonksiyon(argumanlar)" formu kuyruk çağrısıdır. Tabi ki hem fonksiyon hem de argumanlar karmaşık ifadeler olabilir çünkü Lua onları çağrıdan önce değerlendirir. Örneğin bir sonraki çağrı bir kuyruk çağrısıdır:

return x[i].foo(x[j] + a*b, i + j)


Daha önce de söylediğim gibi  kuyruk çağrısı bir goto'dır. Haddi zatında, durum makinesi programlaması için Lua 'daki kuyruk çağrıları uygulaması oldukça yararlıdır. Bu tür uygulamalar her bir durumu bir fonksiyonla temsil eder; durumu değiştirmek için belirli bir fonksiyona gidilir (veya çağrılır). Örnek olarak basit bir labirent oyunu düşünelim. Labirent,  çeşitli odalara sahip, her birinin dört kapısı var: kuzey, güney, doğu ve batı. Her adımda, kullanıcı bir hareket yönüne girer. Bu yönde bir kapı varsa kullanıcı ilgili odaya gider; aksi takdirde program bir uyarı yazdırır. Amaç, başlangıç odasından son odaya gitmektir.

Bu oyun, mevcut odanın durum olduğu tipik bir durum makinesi. Biz her oda için bir fonksiyon ile bu labirenti uygulayabiliriz. Bir odadan diğerine geçmek için kuyruk çağrıları kullanırız. Liste 6.1 dört odalı küçük bir labirentin nasıl yazabileceğimizi gösterir.

ilk odayı çağırarak oyuna başlıyoruz:

room1()



Kuyruk çağrı çıkarsaması olmadan her bir kullanıcı hareketi yeni bir stack seviyesi oluşturacaktı. Bir dizi hamleden sonra stack taşması olurdu.Kuyruk çağrı çıkarsaması ile bir kullanıcının yapabileceği hamle sayısına sınır yok çünkü her hareket aslında geleneksel bir çağrı değil başka bir fonksiyona bir goto gerçekleştirir.

Bu basit oyun için tablolarla oda ve hareketleri betimleyen veri odaklı(data-driven) bir programın daha iyi bir tasarım olduğu kararına varabilirsiniz. Ancak  oyun her odada birkaç özel duruma sahipse bu durum-makine tasarımı daha uygun.

Liste 6.1. labirent oyunu:

function room1 ()
local move = io.read()
if move == "guney" then return room3()
elseif move == "dogu" then return room2()
else
print("gecersiz hareket")
return room1() -- aynı odada kal
end
end

function room2 ()
local move = io.read()
if move == "guney" then return room4()
elseif move == "bati" then return room1()
else
print("gecersiz hareket")
return room2()
end
end

function room3 ()
local move = io.read()
if move == "kuzey" then return room1()
elseif move == "dogu" then return room4()
  else
print("gecersiz hareket")
return room3()
end
end

function room4 ()
print("tebrikler!")
end



Hiç yorum yok:

Yorum Gönder