5 Fonksiyonlar

5 Fonksiyonlar

Fonksiyonlar, Lua'da ifade ve deyimleri soyutlamada ana mekanizmadır. Fonksiyonlar, belli bir görev (bazen diğer dillerde prosedür veya alt yordam olarak adlandırılırlar) veya hesaplama - değerler döndürme ikilisini yerine getirebilir. İlk durumda fonksiyon çağrısını bir deyim olarak  kullanıyoruz; ikinci durumda bir ifade olarak kullanıyoruz:

print(8*9, 9/8)
a = math.sin(3) + math.cos(10)
print(os.date())

Her iki durumda da parantez içinde argümanlar listesini yazıyoruz. Fonksiyon çağrısının argümanları yoksa çağrıyı belirtmek için yine de boş liste () yazmamız gerekir. Bu kural için özel bir durum: eğer fonksiyonun tek argümanı var ve bu argüman bir string öbeği ya da bir tablo kurucusuysa o zaman parantezler isteğe bağlıdır :

print "Merhaba Dunya"   <-->            print("Merhaba Dunya")
dofile 'a.lua'          <-->            dofile ('a.lua')
print [[coksatirli      <-->            print([[coksatirli
        mesaj]]                                 mesaj]])
f{x=10, y=20}           <-->            f({x=10, y=20})
type{}                  <-->            type({})

Lua ayrıca nesne yönelimli çağrılar için özel bir sözdizimi sunar, : operatörü. Bu sadece o.foo(o,x) gibi bir ifadenin o:foo(x) şeklinde yazma yöntemidir. Bu, o.foo çağrısına ilk argüman olarak ekstra o'u ekler. 16. bölümde bu tür çağrıları (ve nesne yönelimli programlamayı) daha ayrıntılı olarak ele alacağız.

Bir Lua programı Lua ve C ikilisinde (veya ev sahibi uygulamaca kullanılan başka bir dilde) tanımlanmış fonksiyonları kullanabilir. Misal standart Lua kütüphanesinden tüm fonksiyonlar  C ile yazılmıştır. Tabi bu durum Lua programcılarını ilgilendirmez: bir fonksiyonu çağırırken Lua 'da tanımlanmış fonksiyonlar ile C'de tanımlanmış fonksiyonlar arasında bir fark yoktur.

Diğer örneklerde gördüğümüz üzere bir fonksiyon tanımlamasının devamdaki gibi genel bir sözdizimi vardır:

function add (a)
 local sum = 0
     for i,v in ipairs(a) do
           sum = sum + v
     end
 return sum
end

Bu sözdiziminde bir fonksiyon tanımlaması bir isime (önceki örnekte add), parametreler listesine ve deyimler listesi olan bir gövdeye sahiptir.

Parametreler, fonksiyon çağrısında geçilen argümanların değerleriyle ilklenen yerel değişkenler olarak çalışır tamamen. Parametrelerinin sayısından farklı argüman sayısıyla bir fonksiyonu çağırabilirsiniz. Lua çoklu atamada yaptığı gibi argümanların sayısını parametrelerin sayısına ayarlar: fazlalık argümanlar atılır; fazlalık parametreler nil alır. Örneğin devamdaki gibi bir fonksiyonumuz varsa

function f(a, b) return a or b end

argümanlardan parametrelere devamdaki eşlemeye sahip olacağız:

ÇAĞRI PARAMETRELER
f(3) a=3, b=nil
f(3, 4) a=3, b=4
f(3, 4, 5) a=3, b=4 (5 es geçilir)


Bu davranış programlama hatalarına (çalışma zamanında kolayca belli eden) yol açabilse de özellikle varsayılan argümanlar için de yararlıdır. Örneğin global bir sayacı artırmak için aşağıdaki fonksiyonu göz önünde bulundurun:

function sayacArttir (n)
 n = n or 1
 sayac = sayac + n
end

Bu fonksiyon varsayılan argümanı olarak 1'e sahiptir; diğer bir deyişle, argümansız sayacArttir() çağrısı sayac 'ı bir artırır. sayacArttir()'ı çağırdığınızda, Lua ilkin n' i nil ile ilkler; or ikinci argümanında sonuçlanır ve haliyle Lua n'e 1 varsayılanını atar.

5.1 Çoklu Sonuçlar

Lua 'nın alışılmadık ama oldukça kullanışlı bir özelliği, fonksiyonların birden fazla sonuç döndürebilmesidir. Lua'daki çeşitli öntanımlı fonksiyonlar çoklu değer döndürür. Bir örnek,  stringde şablon arayan string.find fonksiyonudur. Bu fonksiyon şablonu bulduğunda iki indeks döndürür: şablonun eşleştiği karakter indeksinin başlangıç  ve bitiş yeri. çoklu atama programa iki sonuç almaya imkan verir:

s, e = string.find("merhaba Lua kullanicilari", "Lua")
print(s, e) --> 9 11

Lua'da yazılmış fonksiyonlar da return kelimesinden sonra hepsini listeleyerek birden fazla sonuç döndürebilir. Örneğin bir dizideki en büyük öğeyi bulmak için olan bir fonksiyon hem en büyük  değeri hem de onun konumunu döndürebilir:

function maximum (a)
  local mi = 1                           -- en büyük değerin indeksi
  local m = a[mi]                        -- en büyük değer
  for i,val in ipairs(a) do
     if val > m then
       mi = i; m = val
    end
  end
 return m, mi
end

print(maximum({8,10,23,12,5}))           --> 23 3

Lua daima çağrının durumuna göre fonksiyon sonuç sayısına ayar çeker. Bir fonksiyonu bir deyim olarak çağırdığımızda Lua tüm sonuçları fonksiyondan atar. Bir ifade olarak çağrıyı kullandığınızda Lua sadece ilk sonucu tutar. Tüm sonuçları yalnızca çağrı, ifadeler listesindeki son (veya tek) ifade olduğunda alırız. Bu listeler Lua'da dört yapıda görülür: çoklu atamalar, fonksiyon çağrıları için argümanlar, tablo kurucuları ve return deyimleri. Tüm bu durumları göstermek üzere bir sonraki örnekler için aşağıdaki tanımlamalara sahip olduğumuzu varsayacağız:

function foo0 () end                         -- sonuç döndürmüyor
function foo1 () return "a" end              -- 1 sonuç döndürüyor
function foo2 () return "a","b" end          -- 2 sonuç döndürüyor

çoklu atamada son (veya tek) ifade olarak bir fonksiyon çağrısı, değişkenlerle eşleşmesi için gereken kadar sonuç üretir:

x,y = foo2()                          -- x="a", y="b"
x = foo2()                            -- x="a", "b" es geçilir
x,y,z = 10,foo2()                     -- x=10, y="a", z="b

Bir fonksiyonun sonucu yok veya ihtiyacımız olduğu kadar çok sonucu yoksa Lua eksik değerler için nil ler üretir:

x,y = foo0()                         -- x=nil, y=nil
x,y = foo1()                         -- x="a", y=nil
x,y,z = foo2()                       -- x="a", y="b", z=nil

Listede son öğe olmayan bir fonksiyon çağrısı daima bir sonuç üretir:

x,y = foo2(), 20                     -- x="a", y=20
x,y = foo0(), 20, 30                 -- x=nil, y=20, 30 es geçilir

Bir fonksiyon çağrısı başka bir çağrıda son (veya tek) argüman olduğunda ilk çağrıdan gelen tüm sonuçlar argüman olarak gider. print ile zaten bu yapının örneklerini gördük:

print(foo0())                        -->
print(foo1())                        --> a
print(foo2())                        --> a      b
print(foo2(), 1)                     --> a      1
print(foo2() .. "x")                 --> ax     (şimdi bakacağız)

foo2 çağrısı bir ifade içinde göründüğünde Lua sonuçların sayısını bire ayarlar; bu nedenle son satırdaki birleştirmede(..) yalnızca “a” kullanılır.

print fonksiyonu değişen sayıda argüman alabilir. f(g()) yazar ve f'nin sabit sayıda argümanı varsa Lua daha önce gördüğümüz üzere g'nin sonuçlarının sayısını f'nin parametreleri sayısına ayarlar.

Bir oluşturucu herhangi ayar çekilmeden bir çağrıdan tüm sonuçları toplar:

t = {foo0()}                    -- t = {} (boş tablo)
t = {foo1()}                    -- t = {"a"}
t = {foo2()}                    -- t = {"a", "b"}


Her zaman ki gibi bu davranış yalnızca çağrı listede sonuncu olduğunda gerçekleşir; başka bir konumdaki çağrılar tamamen bir sonuç üretir:

t = {foo0(), foo2(), 4}         -- t[1] = nil, t[2] = "a", t[3] = 4

Son olarak, return f() gibi bir deyim f tarafından döndürülen tüm değerleri döndürür:

function foo (i)
    if i == 0 then return foo0()
    elseif i == 1 then return foo1()
    elseif i == 2 then return foo2()
    end
end

print(foo(1))                  --> a
print(foo(2))                  --> a       b
print(foo(0))                  -- (sonuç yok)
print(foo(3))                  -- (sonuç yok)

Bir çağrıyı fazladan bir parantez çifti içine alarak  tamamen bir sonuç döndürmeye zorlayabilirsiniz:

print((foo0()))                     --> nil
print((foo1()))                     --> a
print((foo2()))                     --> a

Bir return deyiminin döndürülen değer etrafında parantezlere ihtiyaç duymadığına dikkat; oraya yerleştirilen herhangi bir parantez çifti ekstra bir çift olarak hesaba katılır. Bu nedenle, return(f(x)) gibi bir deyim f kaç değer döndürürse döndürsün her zaman tek bir değer döndürür. Belki istediğin bu, belki de değil.

çoklu dönüşlü özel bir fonksiyon da unpack'dir. Bir dizi alır ve indeks 1'den başlayarak dizideki tüm öğeleri sonuç olarak döndürür:

print(unpack{10,20,30})               --> 10 20 30
a,b = unpack{10,20,30}               -- a=10, b=20, 30 es geçilir

unpack için önemli bir kullanım yeri, jenerik çağrı mekanizmasıdır. Jenerik çağrı mekanizması, dinamik olarak herhangi bir argümanla herhangi bir fonksiyonu çağırmanıza imkan verir.  ANSI C'de, misal, jenerik çağrı için bir yöntem yoktur. Değişen sayıda argümanlar alan fonksiyon deklare edebilirsiniz (stdarg.h ile) ve fonksiyonlara işaretçiler kullanarak değişen fonksiyon çağırabilirsiniz. Ancak, değişen sayıda argümanlı bir fonksiyonu çağıramazsınız: C 'de yazdığınız her çağrının sabit argüman sayısı vardır, ve her bir argümanın sabit bir tipi vardır. Lua'da, bir a dizisindeki değişen argümanlarla  değişen f fonksiyonu çağırmak istiyorsanız, sadece şunu yazarsınız:

f(unpack(a))

unpack çağrısı f'nin argümanları haline gelen a'nın tüm değerlerini döndürür. Örneğin şunu çalıştırırsak

f = string.find
a = {"hello", "ll"}

o zaman f(unpack(a)) çağrısı 3 ve 4 'ü döndürür,  string.find("hello","ll") statik çağrısıyla döndürülen aynı sonuçlar.

Lua'a tümleşik bulunan unpack fonksiyonu C de yazılmış olsa da kendi kendini çağırıp yinelemeyle Lua'da da yazabiliriz:

function unpack (t, i)
   i = i or 1
   if t[i] then
       return t[i], unpack(t, i + 1)
   end
end

fonksiyonu ilk kez tek argümanla çağırdığımızda i 1 alır. sonra fonksiyon t[1]'i ve devamında unpack(t,2)'den gelen tüm sonuçları  döndürür, daha sonra  t[2]'i ve devamında unpack(t,3)'den gelen tüm sonuçları döndürür ve bu şekilde nil olmayan son öğeye kadar devam eder.

5.2 Değişken Argüman Sayısı

Lua da bazı fonksiyonlar değişken sayıda argüman alır. Örneğin zaten bir, iki ve daha fazla argümanla print'i çağırmıştık. print, C'de tanımlanmış olsa da Lua'da da değişken sayıda argüman kabul eden fonksiyonlar tanımlayabiliriz.

Basit bir örnek olarak aşağıdaki fonksiyon tüm argümanlarının kümülatifini döndürür:

function add (...)
     local s = 0
     for i, v in ipairs{...} do
          s = s + v
     end
     return s
end

print(add(3, 4, 10, 25, 12))         --> 54

parametre listesindeki üç nokta(...) fonksiyonun değişen sayıda argüman kabul ettiğini gösterir. Bu fonksiyon çağrıldığında tüm argümanları içsel olarak toparlanır; bu toparlanan argümanlara fonksiyonun varargs'ı (varargs-variable arguments-değişken argümanlar) olarak adlandırıyoruz. fonksiyon artık bir ifade olarak üç noktayı kullanarak vararg'larına erişebilir. Örneğimizde {...} ifadesi toparlanan tüm argümanlarla bir dizi üretir. fonksiyon daha sonra öğelerini toplama işlemi için diziyi dolaşır.

... ifadesi mevcut fonksiyonun tüm varargs 'larını döndüren çoklu dönüşlü fonksiyon gibi davranır. Örneğin şu komut

local a, b = ...

ilk iki opsiyonel argümanın değerleriyle(veya argüman yoksa nil) iki lokal değişken yaratıyor.

function foo (a, b, c)

'den

function foo (...)
local a, b, c = ...

'e dönüştürerek aslında Lua'nın normal parametre geçiş mekanizmasını taklit edebiliriz

Perl'nin parametre geçiş mekanizmasını sevenler bu ikinci forma bayılabilir.

 Aşağıdaki gibi bir fonksiyon

function id (...) return ... end

basitçe çağıranına tüm parametrelerini döndürür.  bu, çoklu-değer tanımlayıcı fonksiyondur.
Sonraki fonksiyon çağrıdan önce argümanlarıyla bir ileti yazdırması hariç başka bir foo fonksiyonu gibi davranır:

function foo1 (...)
       print("calling foo:", ...)
       return foo(...)
end

Bu, belli bir fonksiyona olan çağrıları izlemek için yararlı bir hiledir.

Başka bir yararlı örnek görelim. Lua metni biçimlendirme için ayrı  (string.format) ve metin yazmak için ayrı (io.write) fonksiyonlar sağlamıştır. Her iki fonksiyonu tek bir fonksiyonda birleştirmek kolay:

function fwrite (fmt, ...)
      return io.write(string.format(fmt, ...))
end

Noktalardan önce sabit bir parametre olan fmt 'nin varlığını dikkat. Vararg fonksiyonları vararg kısmından önce herhangi sayıda sabit parametreye sahip olabilir. Lua ilk argümanlarını bu parametrelere atar ve yalnızca ekstra argümanlar (varsa) varargs'a gider. Aşağıda, bazı çağrı  örneklerini ve karşılık gelen parametre değerlerini görüyoruz:

ÇAĞRI PARAMETRELER
fwrite() fmt = nil,  varargs yok
fwrite("a") fmt = "a",  varargs yok
fwrite("%d%d", 4, 5) fmt = "%d%d", varargs = 4 ve 5

Değişken argümanları üzerinde iterasyon için add tanımlamımızda yaptığımız gibi bir fonksiyon hepsini bir tabloda toparlamak üzere {..} ifadesini kullanabilir. nadiren de olsa vararg listesi geçerli nil ler içerebileceği zaman da select fonksiyonunu kullanabiliriz. select çağrısı daima bir sabit argümana(seçici), artı değişken sayılı ekstra argümana sahiptir. Seçici  n nolu ise select n'inci ekstra argümanını döndürür; Ya da seçici "#" string'i olmalıdır böylece select ekstra argümanların toplam sayısını döndürür. Aşağıdaki döngü bir fonksiyonun tüm vararg parametreleri üzerinde dolaşmak için select'i nasıl kullanabileceğimizi gösterir:

for i=1, select('#', ...) do
    local arg = select(i, ...)            -- i'inci parametreyi al
   <döngü gövdesi>
end

select("#",...) çağrısı nil ler dahil ekstra parametrelerin tam sayısını döndürür.

Lua 5.0 değişken sayılı argümanlar için farklı bir mekanizmaya sahipti. Bir vararg fonksiyonunun deklarasyon sözdizimi aynıydı, son parametre olarak üç noktayla. Ancak Lua 5.0  ... ifadesine sahip değildi. Bunun yerine bir vararg fonksiyonu varargs ile bir tablo alan arg adında gizli bir yerel değişkene sahipti. Bu tablo ayrıca toplam ekstra argüman sayısının olduğu n alanına sahipti. Bu eski davranışı şu şekilde simüle edebiliriz:

function foo (a, b, ...)
      local arg = {...}; arg.n = select("#", ...)
     <fonksiyon gövdesi>
end

Eski mekanizmanın dezavantajı, program bir vararg fonksiyonunu her çağırdığında yeni bir tablo oluşturmasıdır. Yeni mekanizma ile yalnızca gerektiğinde varargs'ları toparlamak için tablo oluşturabiliriz.

5.3 İsimli Argümanlar

Lua'da parametre geçiş mekanizması pozisyoneldir: bir fonksiyonu çağırdığımızda  argümanlar pozisyonlarıyla parametrelerle eşleştirilir. İlk argüman ilk parametreye değer verir ve böyle devam eder. Ancak bazen argümanları isimle belirtmek yararlıdır. Bunu göstermek için bir dosyayı yeniden isimlendiren os.rename(os kütüphanesinden) fonksiyonunu ele alalım. Çoğu zaman, eskisi hangisi yenisi hangisi, ilk önce hangisiydi unutuyoruz; bu nedenle bu fonksiyonu iki isimlendirilmiş argüman alması için yeniden tanımlamak isteyebiliriz:

-- geçersiz kod
rename(eskisi="temp.lua", yenisi="temp1.lua")

Lua'nın bu sözdizimi için direkt bir desteği yok ancak küçük bir sözdizimi değişikliği ile aynı nihai etkiye sahip olabiliriz. Buradaki olay, tüm argümanları bir tabloda paketlemek ve bu tabloyu fonksiyona tek argüman olarak kullanmaktır. Bu özel sözdizimi Lua 'ca argüman olarak sadece bir tablo oluşturuculu fonksiyon çağrıları için yardımcı hile olarak sağlanmıştır,  :

rename{eskisi="temp.lua", yenisi="temp1.lua"}

Buna göre, yalnızca bir parametreyle rename'i tanımlarız ve bu parametreden gerçek argümanları alırız:
function rename(arg)
    return os.rename(arg.eskisi, arg.yenisi)
end
Bu parametre geçiş stili özellikle fonksiyonun birçok parametreye sahip olması ve çoğunun isteğe bağlı olması durumunda yararlıdır. Örneğin bir GUI kütüphanesinde yeni bir pencere oluşturan bir fonksiyon, adlandırmanın en iyi şekilde kullanıldığı çoğu isteğe bağlı  düzinelerce argümana sahip olabilir:

w = Pencere{ xkonumu=0, ykonumu=0, genislik=300, yukseklik=200,
             baslik = "Lua", arkaplan="blue",
             cerceve = true
             }

Pencere fonksiyonu daha sonra zorunlu argümanları kontrol etme, varsayılan değerler ekleme ve benzerlerini yapma özgürlüğüne sahiptir. yeni pencereyi gerçekte oluşturan fonksiyonun ilkel _Pencere olduğunu varsayarak (ve tüm argümanlara uygun sırayla ihtiyaç duyan), Pencere'i devamdaki liste 5.1'deki gibi tanımlayabiliriz.

Liste 5.1 isimli isteğe bağlı parametrelerle bir fonksiyon

function Pencere (secenekler)
     -- zorunlu seçenekleri kontrol et
      if type(secenekler.baslik) ~= "string" then
             error("Baslik yok")
     elseif type(secenekler.genislik) ~= "number" then
            error("Genislik yok")
     elseif type(secenekler.yukseklik) ~= "number" then
         error("Yukseklik yok")
     end

     -- isteğe bağlılar
     _Pencere(secenekler.baslik,
              secenekler.x or 0,                         -- varsayılan değer
              secenekler.y or 0,                         -- varsayılan değer
              secenekler.genislik, 
              secenekler.yukseklik,
              secenekler.arkaplan or "white",            -- varsayılan
              secenekler.cerceve                         -- varsayılan false (nil)
              )

end

1 yorum: