Ada Programlama Dili — Metotlar (Procedures)
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.
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
vebegin
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
ileend
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.