Ada Programlama Dili — Metotlar (Procedures)

mozgan
Ada Programlama Dili
8 min readJun 6, 2024

Bu makalemizde Ada dilindeki alt programların bir üyesi olan metotları detaylıca inceleyeceğiz. Sırasıyla; parametre alan ve almayan metotlardan başlayıp, parametre modlarına, verilen parametrelerin nasıl aktarıldığına ve son olarak da metot yüklemesinin nasıl yapıldığına değineceğiz.

https://hmhub.in/wp-content/uploads/2018/01/flow-process-e1515418495636.jpg

Metotlar (procedures), Ada programlama dilinde alt programlar kategorisine giren, belirli bir görevi yerine getirmek veya bir dizi eylemi gerçekleştirmek için tasarlanmış kod kalıplarıdır. Metotlar herhangi bir değer döndürmezler, bunun yerine programın durumunu değiştirirler ve yan etkilere neden olurlar. Yazdığımız programları pek çok metot kullanarak daha modüler bir hale getiririz ki bu da kodun okunabilirliğini artırdığı gibi bakımını da kolaylaştırır.

Sözdizimi

procedure <Procedure_Name> [(<Params>)] is
-- metotun öğeleri
begin
-- metotun içeriği
end <Procedure_Name>;

Yukarıda da görüleceği üzere, metotların sözdizimi oldukça basittir. Şimdi bu sözdizimine bakarak bir metodun neleri içerdiği, neler yapıp neler yapamadığı hakkında açıklamalarda bulunalım:

  • Bir metot procedure anahtar kelimesi ile başlar.
  • <Procedure_Name>, metodun adını temsil eder.
  • <(Params)>, metoda verilen parametre listesini içerir. Ayrıca bir metot parametre alabileceği gibi parametresiz de olabilir.
  • is ve begin anahtar kelimeleri arasında metodun öğeleri tanımlanır. Bu öğeler metot içerisinde kullanılacak olan türler, alt türler, değişkenler olabileceği gibi alt programlar da olabilir. Şayet metot içerisinde bunların herhangi birine ihtiyaç yok ise bu alan boş bırakılır.
  • begin ile end anahtar kelimeleri arasında metodun içeriği bulunur ki burada çeşitli algoritmalar icra edilir.

Bunlara ek olarak, parametrelerin ikiye ayrıldığını da ayrıca belirtelim:

  • Gerçek parametreler, metodu çağırırken verdiklermizdir; yani metodun argümanları.
  • Formel parametreler ise metodun tanımında bulunanlardır; yani metot içerisinde kullanılacak olanlar.

Parametreler ve Modları

Bir metot birden çok parametre alabilir. Bazen bu parametreler öntanımlı değereler de sahip olabilirler. Örneğin aşağıdaki gibi:

procedure Say_Hello (Str : String := "World") is
begin
Put_Line ("Hello " & Str & "!");
end Say_Hello;

Eğer Say_Hello metodu parametresiz çağırılırsa ekrana “Hello World!” mesajını yazar. Şayet parametre olarak bir kelime veya metin girersek, örneğin “Ada” kelimesi ile metodu çağırırsak o zaman da ekrana “Hello Ada!” mesajını yazar.

Bir metota verilen parametreler çeşitli modlara sahip olabilirler. Bunlar Ada’da in, out ve in out olarak tanımlanmıştır ve farklı işlevlere sahiptirler. Şimdi bu modları detaylıca inceleyelim.

in Modu

Yukarıdaki örnekte dikkat ederseniz Str parametresi için herhangi bir mod kullanılmamış. Böyle bir durumda Str parametresi otomatik olarak in modundadır. Ayrıca öntanımlı değere sahip bir parametre de her zaman bu modda kullanılır. Yani bu örnektekiStr parametresi için farklı bir mod istesek de kullanamayız. Eğer in dışında bir mod kullanmaya kalkılırsa derleyici şöyle bir hata verecektir:

error: default initialization only allowed for "in" parameters

in moduna sahip bir parametre, yalnızca ek bilgi amacıyla metota verilir ve metot içerisinde kullanıma hazırdır. Yani verilen parametrenin o anki değeri metot için sadece bir önem arz eder. Fakat bu parametre metot içerisinde değişime kapalıdır. Eğer böyle bir parametre değiştirilmeye çalışılırsa, derleme aşamasında aşağıdaki gibi bir hata ile karşılaşılır:

error: assignment to "in" mode parameter not allowed

out Modu

Programın herhangi bir yerinde tanımlanmış olan bir değişken, parametre olarak verilen bir metot içerisinde güncellenmesi suretiyle önemli kabul edilen bir değere sahip olması istenebilir. Böyle durumlar için out modunu kullanırız. Diğer bir manası da bu parametrenin metot için değerli bir bilgi taşımamasıdır ki, burada önemli olan verilen parametrenin metot sonunda elde edeceği bilgidir. Eğer bu parametreyi güncellemeden önce kullanmaya çalışırsanız istenmeyen sonuçlar ile karşılaşabilirsiniz. Aşağıdaki kodun çıktısını incelerseniz ne demek istediğimizi daha iyi anlarsınız:

with Ada.Text_IO; use Ada.Text_IO;

procedure Main is

procedure Foo (X : out Integer) is
begin
Put_Line ("Foo (1): " & X'Image);
X := 100;
Put_Line ("Foo (2): " & X'Image);
end Foo;

K : Integer := 10;
begin
Put_Line ("Main (1): " & K'Image);
Foo (K);
Put_Line ("Main (2): " & K'Image);
end Main;

-- Ekran Çıktısı:
-- Main (1): 10
-- Foo (1): 32764
-- Foo (2): 100
-- Main (2): 100

Örnekte de görüleceği gibi Foo metodunun parametresi X, modifiye edilmeden önce okunmuş; ve bu programın çalıştığı zamandaki değeri 32764 imiş. Eğer programı tekrar tekrar çalıştırırsanız bu değer değişecektir. Şayet böyle bir kod yazmış iseniz zaten derleyici de sizi şu şekilde uyaracaktır:

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

Buradan da anlaşılıyor ki out modu ile verilen bir parametre aslında bir referans değil! Öyle olsaydı X parametresinin değeri metota verilen K değişkeninki ile aynı olurdu. Bunun neden böyle olduğunu yukarıdaki örnek üzerinden anlatalım: öncelikleFoo metodunda kullanılacak olan X parametresi için bellekten bir yer ayrılıyor. Bu parametre out moduna sahip olduğu için zaten öntanımlı bir değer de alamaz. Bunun neden böyle olduğunu in modunda anlattık. O halde X parametresi o an bellekte bulunduğu yerdeki değere sahiptir. Sonrasında metot içerisinde X parametresi bir şekilde modifiye edilir. Ne zaman Foo metodu biter, işte o an X parametresinin değeri Main içerisinde tanımlı olan K değişkeninin içerisine kopyalanır.

in out Modu

Bu modu, yukarıda anlattığımız iki modun birleşimi şeklinde de düşünülebilir. Yani bir metoda verilen parametre in out modunda ise, o parametrenin metota aktarıldığı zamanki değeri metot için bir önem arz ettiği düşünülür. Aynı şekilde, bu parametrenin metot içerisinde gücellenmesi sonucu yeni değerinin, metodun bitiminden sonra da önemli olması gerekir. Kısaca demek istiyoruz ki; in out moduna sahip bir parametrenin içeriği hem metot için hem de metot sonrası için önemli bilgi taşımalıdır.

Farz edelim ki Integer türünden iki değişkenin değerlerini yer değiştiren bir metot yazmak istiyoruz; yani programımızda meşhur swap işlemini yapmamız gerekiyor. O halde, böyle bir işlem için verilecek olan parametrelerin metot öncesi değerleri önemli olduğu gibi metot sonrası da önemlidir. Yani her iki parametrenin de in out moduna sahip olması gerekir.

with Ada.Text_IO; use Ada.Text_IO;

procedure Main is

procedure Swap (A, B : in out Integer) is
Temp : Integer;
begin
Temp := A;
A := B;
B := Temp;
end Swap;

A : Integer := -10;
B : Integer := 20;

begin

Put_Line ("A: " & A'Image & ", B: " & B'Image);
Swap (A, B);
Put_Line ("A: " & A'Image & ", B: " & B'Image);

end Main;

-- Ekran çıktısı:
-- A: -10, B: 20
-- A: 20, B: -10

Eğer Swap metoduna verilen parametrelerin modu in out değil de yalnızca out olsaydı değerler bambaşka olurdu. Benzer şekilde, eğer bu parametreler sadece in modunda olsaydı aşağıdaki gibi bir derleme hatası ile karşılacaktık:

main.adb:9:07: error: assignment to "in" mode parameter not allowed
main.adb:10:07: error: assignment to "in" mode parameter not allowed

Kopya mı Referans mı?

Yukarıda da bahsettiğimiz gibi out moduna sahip olan parametreler esasında birer kopya olarak aktarılıyorlar. Peki ya diğer modlar?

Aşağıdaki programda her bir mod için ayrı metotlar yazılmış. Hangi moda sahip parametrenin çağırıldığı metota nasıl aktarıldığını bi inceleyelim:

with Ada.Text_IO; use Ada.Text_IO;
with System.Address_Image;

procedure Main is

procedure Check_Passing (Formal : System.Address; Actual : System.Address)
is
begin
if System.Address_Image (Formal) = System.Address_Image (Actual) then
Put_Line ("Parametre referans olarak aktarılmış.");
else
Put_Line ("Parametre kopya olarak aktarılmış.");
end if;
end Check_Passing;

procedure In_Mode (A : in Integer; Addr : System.Address) is
begin
Put ("in modlu metot: ");
Check_Passing (Formal => A'Address, Actual => Addr);
end In_Mode;

procedure Out_Mode (A : out Integer; Addr : System.Address) is
begin
Put ("out modlu metot: ");
Check_Passing (Formal => A'Address, Actual => Addr);
end Out_Mode;

procedure In_Out_Mode (A : in out Integer; Addr : System.Address) is
begin
Put ("in out modlu metot: ");
Check_Passing (Formal => A'Address, Actual => Addr);
end In_Out_Mode;

Temp : Integer := 0;

begin
In_Mode (Temp, Temp'Address);
Out_Mode (Temp, Temp'Address);
In_Out_Mode (Temp, Temp'Address);
end Main;

-- Ekran çıktısı:
-- in modlu metot: Parametre kopya olarak aktarılmış.
-- out modlu metot: Parametre kopya olarak aktarılmış.
-- in out modlu metot: Parametre kopya olarak aktarılmış.

Görüleceği üzere Ada’da öntanımlı olan aktarma yöntemi aslında kopyalamadır. Yani metodun tanımında bulunan her bir parametre için bellekte ayrı bir yer ayrılır. Bu demek oluyor ki, parametrelere verilen modlar ile referans olarak aktarımın herhangi bir bağlantısı yok. O zaman şöyle bir soru karşımıza çıkıyor: acaba Ada’da, diğer dillerdeki gibi referans aktarımı yok mu? Elbette var; ve bu iş aliased anahtar kelimesi yardımıyla yapılır.

aliased Parametreler

Eğer bir değişken ve onun için kullanılacak olan parametre, ikisi de aliased anahtar kelimesi ile tanımlı ise, tanımlandıkları türden bağımsız olarak her zaman referans şeklinde kullanılırlar. Şayet yukarıdaki örneği aşağıdaki gibi yazarsak Temp değişkenini referans olarak kullanabiliriz:

with Ada.Text_IO; use Ada.Text_IO;
with System.Address_Image;

procedure Main is

procedure Check_Passing (Formal : System.Address; Actual : System.Address)
is
begin
if System.Address_Image (Formal) = System.Address_Image (Actual) then
Put_Line ("Parametre referans olarak aktarılmış.");
else
Put_Line ("Parametre kopya olarak aktarılmış.");
end if;
end Check_Passing;

procedure In_Mode (A : aliased in Integer; Addr : System.Address) is
begin
Put ("in modlu metot: ");
Check_Passing (Formal => A'Address, Actual => Addr);
end In_Mode;

procedure Out_Mode (A : aliased out Integer; Addr : System.Address) is
begin
Put ("out modlu metot: ");
Check_Passing (Formal => A'Address, Actual => Addr);
end Out_Mode;

procedure In_Out_Mode (A : aliased in out Integer; Addr : System.Address) is
begin
Put ("in out modlu metot: ");
Check_Passing (Formal => A'Address, Actual => Addr);
end In_Out_Mode;

Temp : aliased Integer := 0;

begin
In_Mode (Temp, Temp'Address);
Out_Mode (Temp, Temp'Address);
In_Out_Mode (Temp, Temp'Address);
end Main;

-- Ekran çıktısı:
-- in modlu metot: Parametre referans olarak aktarılmış.
-- out modlu metot: Parametre referans olarak aktarılmış.
-- in out modlu metot: Parametre referans olarak aktarılmış.

Dikkat ederseniz, hem Temp parametresi hem de çağırılan metotta kullanılacak olan parametre, ikisi de aliased özelliğine sahiptir.

Hemen şunu da belirtelim; yukarıdaki örnekte de görüleceği üzere, bir metot, formal parametreler ile gerçek parametrelerin => ayracıyla eşleştirilmek suretiyle de çağırılabilir:

Check_Passing (Formal => A'Address, Actual => Addr);

Programlama dillerinde kullanılan farklı parametre aktarımları hakkında daha fazla bilgiye ihtiyaç duyulması halinde şu sayfa okunabilir.

Son olarak diyebiliriz ki, Ada’da bazı türler otomatik olarak referans aktarımına sahiptirler: örneğin görev (task) türleri. Buna karşılık, diğerler türleri referans olarak aktarmak için aliased kullanılmalıdır. Ancak referans konusuna daha sonra tekrar dönecemizden ötürü şimdilik burada bırakıyoruz.

Metot Yüklemesi

Farklı türlere sahip değişkenler için aynı isme sahip pek çok metot yazılabilir. Bu mekanizmaya metot yüklemesi (overloading) denir. Örnek olarak aşağıdaki kodu inceleyebilirsiniz:

procedure Main is

type Gun is (Pazartesi, Sali, Carsamba, Persembe, Cuma, Cumartesi, Pazar);
subtype Hafta_Ici is Gun range Pazartesi .. Cuma;

type My_Int is range 0 .. 100;
type Byte is mod 256;

type Arr is array (My_Int) of Integer;
type Rec is null record;

procedure Foo (Temp : Gun) is null;
procedure Foo (Temp : My_Int) is null;
procedure Foo (Temp : Byte) is null;
procedure Foo (Temp : Arr) is null;
procedure Foo (Temp : Rec) is null;
procedure Foo (Temp : Integer) is null;
procedure Foo (Temp : Float) is null;
procedure Foo (Temp : String) is null;

A : Arr := (others => 0);
B : Rec;
C : Gun := Cumartesi;
D : Hafta_Ici := Pazartesi;
E : My_Int := 0;
F : Byte := 0;
G : Integer := 0;
H : Float := 0.0;
J : String (1 .. 10) := (others => ' ');

begin
Foo (A);
Foo (B);
Foo (C);
Foo (D);
Foo (E);
Foo (F);
Foo (G);
Foo (H);
Foo (J);
end Main;

Yukarıdaki programa dikkat ederseniz, Hafta_Ici için ayrı bir metodun yazılmadığını hemen göreceksiniz. Çünkü Hafta_Ici aslında ayrı bir tür değildir. Bundan ötürü ayrı bir metot yazmamıza da gerek kalmıyor; ve bu alt türe ait bir değişken için Foo (Temp : Gun) metodu çağırılır. Şayet buna rağmen, örneğin Foo (Temp : Hafta_Ici) şeklinde bir metot yazarsak, Hafta_Ici alt türünden üretilen bir değişken bu iki metot için de geçerli olacaktır. Bundan ötürü belirsiz bir durum karşımıza çıkacak ve kodumuz derlenmeyecek.

Ek olarak şunu da söyleyelim; eğer bir metodun içeriğini, yukarıdaki örnekte olduğu gibi, daha sonra yazmak istiyorsak, bu metodu yalnızca tanım seviyesinde bırakmak için null anahtar kelimesi kullanılır. Ve böyle tanımlı metotlar program içerisinde herhangi bir etkiye sahip değildirler.

Bitirirken

Metotlar, tekrarlayan bir kod parçası için kullanılacağı gibi özel bir görevi yerine getirmek için de kullanılırlar. Metot yazarken belki de dikkat edilmesi gereken hususlar şunlardır:

  • Metot için anlamlı bir isim kullanmak.
  • Parametrelerin modlarını doğru belirlemek.
  • Global değişkenler için yan etki oluşturmaktan kaçınmak.
  • Bir metot mümkün olduğunca tek bir işi üstlenmeli.

Bu maddelere dikkat edildiği sürece yazılan program hem daha okunaklı hem de bakımı kolay olur. Ayrıca program çalışırken ortaya çıkacak olan hataları ayıklama işi de bir o kadar kolaylaşır.

--

--