Ada Programlama Dili — Fonksiyonlar (Functions)

mozgan
Ada Programlama Dili
8 min readJun 6, 2024

Şimdi de Ada dilindeki alt programların diğer bir üyesi olan fonksiyonları inceleyeceğiz. Bu yazının düzeni metotlar için yazılan makale ile paralellik göstermekte. Bu yüden önce diğer makalenin okunmasında fayda var. Ek olarak; bu yazımızda, fonksiyonları kullanarak operatör yükleme ve ezme işlemlerinin ne zaman ve nasıl yapıldığı hakkında da bilgi verilmiştir.

https://ocw.ui.ac.id/pluginfile.php/8010/mod_label/intro/icon8.JPG

Ada programlama dilinde fonksiyonlar, belirli bir işlemi gerçekleştiren ve sonunda bir değer döndüren kod kalıplarıdır. Yazılan bir programda fonksiyon kullanmak suretiyle kodumuzu daha modüler ve okunabilir bir hale getiririz. Ayrıca fonksiyonlar, karmaşık işlemleri küçük ve yönetilebilir birimlere ayırmamıza da yardımcı olurlar.

Sözdizimi

Fonksiyonlar, metotlar gibi alt program kategorisine girdiği için sözdizimleri birbirlerine çok benzer:

function <Function_Name> [(<Params>)] return <retval> is
-- fonksiyonun öğeleri
begin
-- fonksiyonun içeriği
end <Function_Name>;

Dikkatli bir göz burada return anahtar kelimesinin kullanıldığını hemen görecektir. Şimdi bu sözdizimini daha anlaşılır kılmak için açıklayalım:

  • Bir fonksiyon function anahtar kelimesi ile başlar.
  • <Function_Name>, fonksiyonun adını temsil eder.
  • <(Params)>, fonksiyona verilen parametrelerin bir listesidir. Bir fonksiyon parametreli olacağı gibi parametresiz de olabilir.
  • <retval>, fonksiyondan dönecek olan değerin türünü belirtir.
  • is ile begin anahtar kelimeleri arasında fonksiyonun öğeleri bulunur. Bu öğeler fonksiyon içinde kullanılacak olan türler, alt türler, değişkenler, metotlar ve fonksiyonlar olabilirler. Eğer fonksiyon içerisinde herhangi bir öğeye ihtiyaç yoksa, bu alan boş bırakılır.
  • begin ve end arasında ise fonksiyonun algoritması bulunur; ve fonksiyon, buradaki algoritma için var edilmiştir.

Bunlara ek olarak, fonksiyonların parametreleri metotlarınki gibi ikiye ayrılır: Gerçek parametreler ve formel parametreler.

Parametreler ve Modları

Bir fonksiyon parametresiz olabileceği gibi bir veya birden çok parametre de alabilir. Bu parametreler kimi zaman öntanımlı değerlere de sabip olabilirler.

with Ada.Text_IO; use Ada.Text_IO;

procedure Main is

function Increment_By (Val : Integer; Inc : Integer := 1) return Integer is
begin
return Val + Inc;
end Increment_By;

X : Integer := 10;
Y : Integer := 20;

begin

Put_Line (Increment_By (X)'Image);
Put_Line (Increment_By (Y, 2)'Image);

end Main;

-- Ekran Çıktısı:
-- 11
-- 22

Yukarıdaki örnekte de görüleceği üzere Increment_By fonksiyonu iki parametre almakta, ve ikinci parametresine ilk değer olarak 1 atanmış. Bu fonksiyonun amacı da iki parametreyi toplayıp, geriye bu toplamın sonucunu döndürmesi. Eğer bu fonksiyona tek parametre verilirse, o zaman ilk parametre 1 artırılır. Buna karşılık, eğer iki parametre verilirse, ilk parametre ikincisi kadar artırılır.

Ada programlama dilinde fonksiyonlar, birer ifade gibi yorumlandığı için is anahtar kelimesinden sonra parantez kullanarak da yazılabilirler. O halde yukarıdaki Increment_By fonksiyonunu şu şekilde de yazabiliriz:

function Increment_By (Val : Integer; Inc : Integer := 1) return Integer is (Val + Inc);

Elbette ki böyle bir yazım uzun fonksiyonlar için pek kullanışlı değil. Fakat kısa fonksiyonlar için vazgeçilmez bir yazım şekli.

Bir fonksiyona verilen parametreler in, out veya in out olmak üzere bu üç moddan birisine sahiptirler. Şimdi bu modlara da kısaca değinelim.

in Modu

Bu moda sahip bir parametre, çağırıldığı fonksiyon için yalnızca bilgi değeri taşır ve herhangi bir değişime de kapalıdır. Eğer bir parametrenin modu belirtilmemiş ise, bu parametrenin modu derleyici tarafından in olarak yorumlanır. Ayrıca öntanımlı değere sahip olan parametreler de in modundadır. Örneğin, yukarıdaki örnekteki Inc parametresi her zaman in modunda olmak zorundadır.

out Modu

Bir fonksiyona out modunda parametre verebilmek için önceden tanımlanan bir değişkene ihtiyaç vardır.

Şimdi yukarıdaki örnekte bulunan Increment_By fonksiyonunu aşağıdaki gibi yazdığımızı düşünelim:

function Increment_By (X : out Integer; Y : in Integer := 1) return Integer is
begin
X := X + Y;
return X;
end Increment_By;

Böyle bir yazımda öncelikle derleyici sizi uyaracaktır:

warning: "X" may be referenced before it has a value [enabled by default]

Yani out moduna sahip olan X değişkeni modifiye edilmeden önce okunursa, çağıldığı değere sahip olmayabilir. Zaten out modunun özelliği de parametrenin değerinin fonksiyon çağrılmadan önce önemli olmamasıydı. Böyle bir fonksiyondan istenmeyen bir değer döner. Ben bu programı her defasında çağırdığımda şu değerleri elde ettim: 29254, 30317, 31799 vb. Bunun sebebine metotlar için yazdığımız makalede değindik.

Diğer önemli husus; out moduna sahip bir parametre, çağırıldığı yerde bir değişken olarak tanımlı olmalıdır. Şayet Increment_By fonksiyonunu aşağıdaki gibi çağırırsak kodumuz derlenmeyecek ve bir hata ile karşılaşacağız:

Put_Line (Increment_By (3, 2)'Image);

-- Hata mesajı:
-- error: actual for "X" must be a variable

in out Modu

Bu modun özelliği yukarıdakilerin sahip olduğu kısıtlamaları ortadan kaldırmaktır. Yani bir fonksiyona in out modunda verilen parametrenin değeri, o fonksiyon içerisinde ve sonrasında kullanmak için önem arz eder.

in out modunun kullanımı fonksiyonlarda pek nadir olsa da şöyle bir örnek verebiliriz: bir karakter dizisi (string) içerisinde belirli bir karekteri bulup yerine başka bir karekteri yerleştiren bir fonksiyon yazmak istiyoruz. Şayet aranan karakter bulunamamış ise fonksiyonumuz geriye False değerini, aksi halde True değerini döndürsün. Elbette ki verilen karakter dizisinin içeriği de fonksiyon sonrası için önemli olsun. Şimdi bu fonksiyonu aşağıdaki gibi yazabiliriz:

function Find_And_Replace (S       : in out String;
Find : in Character;
Replace : in Character)
return Boolean
is
Result : Boolean := False;
begin
for I in 1 .. S'Length loop
if S (I) = Find then
S (I) := Replace;
Result := True;
end if;
end loop;

return Result;
end Find_And_Replace;

Find_And_Replace fonksiyonunun parametresi olan S‘nin değeri, hem bu fonksiyon için hem de fonksiyondan sonrası için önemli olduğundan ötürü in out modundadır.

Bu noktada şunu da belirtmekte fayda var: bir fonksiyon çağırıldığı yere değer döndürdüğü için, bu dönüş değeri ya bir değişkene atanmalı veya bir şekilde kullanılmalı. Yani bu değer yok sayılamaz! Aksi halde derleyici hata verecektir. Örneğin yukarıdaki Find_And_Replace fonksiyonunun dönüş değerini kullanmadan çağırırsak şöyle bir hata ile karşılaşırız:

Find_And_Replace (S => Str, Find => '-', Replace => 'a');

-- Hata mesajı:
-- error: cannot use call to function "Find_And_Replace" as a statement
-- error: return value of a function call cannot be ignored

Bir parametre hangi modda olursa olsun, o parametre ile eşleşen değişken çağırıldığı fonksiyona kopyalanarak aktarılır. Bu konuyu, metotları incelediğimiz makalede açıkça anlattığımız için burada tekrar etmeyi gereksiz gördük. Şayet parametrelerin referans olarak aktarılmasını istersek aliased anahtar kelimesi kullanılır. Tekrar belirtmekte fayda var; aliased anahtar kelimesi hem değişkene hem de fonksiyonun parametresine verilmelidir. Aksi halde referans olarak aktarım sağlanmaz.

Fonksiyon Yüklemesi

Fonksiyonlar da aynen metotlar gibi, farklı türler için aynı ismi kullanarak birden çok defa yazılabilirler:

with Ada.Text_IO; use Ada.Text_IO;

procedure Main is

function Foo (Temp : Float) return Integer is (Integer(Temp));
function Foo (Temp : Integer) return Integer is (Integer(Temp));

function Foo (Temp : Float) return String is (Temp'Image);
function Foo (Temp : Integer) return String is (Temp'Image);

X : Integer;

begin

X := Foo(3.14);
Put_Line ("X: " & X'Image);

Put_Line ("Geri dönüş değeri: " & Foo(3.14));

end Main;

-- Ekran çıktısı:
-- X: 3
-- Geri dönüş değeri: 3.14000E+00

Yukarıdaki örneği dikkatlice incelerseniz, örneğin C++’da olmayan bir fonksiyon yükleme mekanizmasıyla karşı karşıya olduğunuzu göreceksiniz: bir fonksiyon, verilen parametrelerin türleri için yüklenebildiği gibi geri dönüş türleri için de yüklenebilir ;-)

Burada yine tekrar etmekte fayda var; bir fonksiyon hem belirli bir tür hem de onun alt türü için yükleme yapılırsa hata ile karşılaşırsınız. Örneğin Long_Long_Float veya Natural gibi alt türler için fonksiyon yüklemesi yaparsak derleyici hata verecektir:

procedure Main is

function Foo (Temp : Float) return Integer is (Integer (Temp));
function Foo (Temp : Long_Long_Float) return Integer is (Integer (Temp));
function Foo (Temp : Integer) return Integer is (Integer (Temp));

X : Integer;

begin

X := Foo (3.14);

end Main;

-- Derleme hatası:
-- error: ambiguous expression (cannot resolve "Foo")
-- error: possible interpretation at line 4
-- error: possible interpretation at line 3

Görüleceği üzere 3.14 sayısı derleyici tarafından hem Float hem de Long_Long_Float olarak yorumlanmış.

Operatör Yüklemesi

Ada dilinde birçok operatör tanımlıdır. Bu operatörler aritmetik, karşılaştırma veya mantık işlemleri için kullanılırlar. Operatörler için şöyle bir tanım yapabiliriz: bir operatör, bir veya iki değişken ile işlem yapıp sonuç olarak bir değer üretir. O halde, bu tanımı tek veya iki parametre alan fonksiyonlar için de kullanabiliriz; çünkü bu tür fonksiyonlar da aldığı parametreler ile işlem yapıp çağırıldığı yere değer döndürürler.

Kimi operatör, bazı türler için dil tarafından tanımlıyken diğerleri için tanımlı değildir. Örneğin; aynı türdeki dizileri birleştirmek için & operatörü dil tarafından tanımlıdır ve hemen kullanılabilir; buna karşılık, bu dizileri toplamak için + operatörü tanımsızdır. O halde biz de bu operatörleri istediğimiz türler için istediğimiz gibi tanımlayabiliriz; yani operatör yüklemesi (overloading) yapabiliriz.

Aşağıdaki örnekte Vector olarak tanımlanan bir dizi için + operatörü yüklemesi yapılmış:

with Ada.Text_IO; use Ada.Text_IO;

procedure Main is

type Vector is array (Integer range <>) of Float;

function "+" (Left, Right : Vector) return Vector is
Temp : Vector (Left'Range) := (others => 0.0);
begin
if Left'Length /= Right'Length then
raise Constraint_Error;
end if;

for I in Left'Range loop
Temp (I) := Left (I) + Right (I);
end loop;

return Temp;
end "+";

function "+" (Left : Vector; Right : Integer) return Vector is
Temp : Vector (Left'Range) := (others => 0.0);
begin
for I in Left'Range loop
Temp (I) := Left (I) + Float (Right);
end loop;

return Temp;
end "+";

V1 : Vector := (1.1, 2.2, 3.3, 4.4, 5.5);
V2 : Vector := (10.10, 20.20, 30.30, 40.40, 50.50);
Temp : Vector (V1'Range);

C : constant Integer := 3;

begin

Temp := V1 + V2;
Put_Line (Temp'Image);

Temp := V1 + C;
Put_Line (Temp'Image);

end Main;

-- Ekran çıktısı:
-- [ 1.12000E+01, 2.24000E+01, 3.36000E+01, 4.48000E+01, 5.60000E+01]
-- [ 4.10000E+00, 5.20000E+00, 6.30000E+00, 7.40000E+00, 8.50000E+00]

Bu operatörlerden birisi, Vector türüne ait iki parametreyi alarak, bunları toplar. Diğeri ise Vector türünden bir değişkenin tüm elemanlarına Integer türünden bir değişken eklemek için kullanılır.

Operatör Ezme

Ada dili, tanımlanmış olan operatörleri tekrar yazmak suretiyle yok sayarak ezme (overriding) işlemine de olanak sağlar. Şöyle bir örnek verelim ve daha anlaşılır kılalım: farz edelim ki sınırları belirlenmiş bir türümüz var. Ve Ada dilinde toplam işlemi yapmak amacıyla bu tür için + operatörü tanımlı olsun. Programımız da bu türden üretilen iki değişkeni toplasın:

with Ada.Text_IO; use Ada.Text_IO;

procedure Main is

type My_Int is range 0 .. 100;

A : My_Int := 60;
B : My_Int := 70;
Sum : My_Int := 0;

begin

Sum := A + B;
Put_Line ("Toplam: " & Sum'Image);

end Main;

Yukarıdaki örnekğe dikkat ederseniz, 60 ve 70 sayılarının toplamı 130 olacağından ötürü My_Int türünün üst sınırı aşılmış. Bu durumda programımız çalışırken şöyle bir hata verecektir:

raised CONSTRAINT_ERROR : main.adb:12 overflow check failed

Şimdi farz edelim ki böyle bir taşma olduğunda hata almak yerine My_Int türünün üst sınırı ile işlemlere devam etmek istiyoruz. O halde dilin bize sunduğu + opertörünü yok sayıp, kendi operatörümüzü yazmamız gerekiyor. Bu işlemi yapan kodu inceleyiniz:

function "+" (X, Y : My_Int) return My_Int is
Left : constant Integer := Integer (X);
Right : constant Integer := Integer (Y);
Temp : constant Integer := Left + Right;
begin
if Temp > Integer (My_Int'Last) then
return My_Int'Last;
else
return My_Int (Temp);
end if;
end "+";

Burada yapılan esasında çok basit bir işlem: My_Int türüne ait olan parametreler bir üst türe dönüştürülmek suretiyle sınır aşımının önüne geçilmiş. Bu sayede A + B toplamının sonucu, eğer My_Int türünün üst sınırını geçerse, hata üretmek yerinde bize bu üst sınır değerini verecek.

Bitirken

Fonksiyonlar da metotlar gibi, tekrarlayan kod parçalarını düzenlemek veya özel bir görevi yerine getirmek için kullanılırlar. Ancak tekrar vurgulamakta fayda var; fonksiyonlar, metotların aksine her zaman bir değer döndürmek zorundadırlar! Ve fonksiyondan dönen değer bir şekilde kullanılmak zorundadır, yani yok sayılamaz! Bunlara ek olarak, fonksiyonlar sayesinde operatör yüklemesi ve ezmesi de yapabiliriz.

--

--