Ada Programlama Dili — Modüler Yapı

mozgan
Ada Programlama Dili
8 min readJun 4, 2024

Ada, pek çok programlama dilinde olduğu gibi modüler programlama tekniğine sahip bir dildir. Bu makalemizde Ada programlarının modüler yapısına kısaca değineceğiz. Gelecek yazılarımızda bu yapıları daha detaylı incelemeyi planlıyoruz.

https://www.yankodesign.com/images/design_news/2023/07/auto-draft/kerbal_space_program_shuttle_4.jpg

Yazılım mühendisliğinin en önemli amaçlarından biri mevcut program kodlarını tekrar tekrar kullanmaktır. Yani bir programı daha küçük birimlere ayırmak suretiyle modüler bir hale getiririz ki bu sayede her birimi çok defa kullanma şansını da elde ederiz. Böylece yazılacak olan yeni programların maliyeti de azaltılmış olur.

Ada dilini güvenilir yapan etmenlerden birisi de modüler yapısıdır. Aşağıda Ada programlarının modüler yapısı hakkında bazı bilgiler sunulmuştur. Detaylara geçmeden önce hemen şunu belirtelim; modüler yapılar Ada dilinde iki parçadan oluşur: tanım (decleration) ve içerik (context). Daha açık bir ifadeyle söylersek; tanım kısmında her zaman içerik kısmında kullanılacak olan öğeler bulunur. Örneğin alt türler, değişkenler v.s. tanımlanır. Eğer herhangi bir tanıma gerek duyulmazsa elbette boş bırakılır.

Alt Programlar

Bir alt program, başka bir alt program tarafından çağırılma suretiyle yürütülebilen program birimidir. Ayrıca alt programlar çok kullanışlı bir soyutlama tekniğidir ve kodun okunabilirliğini de artırırlar.

Ada programlama dilinde karşımıza iki çeşit alt program çıkar. Bunları şöyle sıralayabiliriz:

  • Metotlar (procedures)
  • Fonksiyonlar (functions)

Bu ikisi esas itibariyle aynı gibi gözükse de temelde farklılık gösterirler. En temel ayrım fonksiyonların bir değer döndürmesidir ki metotların böyle bir olanağı yoktur. Daha teknik bir ifadeyle söylemek gerekirse, metotlar deyimdir (statement), buna karşılık fonksiyonlar ise birer ifadedir (expression) çünkü sonuç olarak bir değer üretirler ve bu değeri çağırıldığı yere geri döndürürler. Bu sebepten ötürü metotlar, daha çok yan etkilerinden (side effect) dolayı kullanılırlar.

Metotların ve fonksiyonların Ada dilindeki sözdizimleri aşağıdaki gibidir:

-- Metot tanımı
procedure <Procedure_Name> [(<Params>)] is
-- metudun öğeleri
begin
-- metodun içeriği
end <Procedure_Name>;

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

Yukarıdaki tanımları karşılaştırırsanız aradaki benzerlikler ve farklar hemen gözünüze çarpacaktır.

Bir alt program çağırıldığında programın akışı şu şekilde gerçekleşir: önce is ve begin arasında tanımlı olan öğeler oluşturulur. Şayet bu öğelerden bir kısmı; mesela bir değişken bir fonksiyona bağlı ise, önce o fonksiyon çağırılır ve bu sayede değişken hayat bulur. Daha sonra da begin ile end arasında kalan kısım, yani alt programın içeriği icra edilir.

Bu noktada bazı önemli hususlara değinmekte fayda var:

  • Alt programdan oluşan bir dosya içerisinde yalnızca bir adet metot veya fonksiyon bulunabilir. Fakat bunun yanında, metodun veya fonksiyonun öğesi olarak birden çok alt program yazılabilir. Örneğin; tek bir dosya içine aşağıdaki gibi kod yazarsanız derleme sırasında hata ile karşılaşırsınız:
with Ada.Text_IO; use Ada.Text_IO;

-- Fonksiyon tanımı
procedure Selamlama (Str : String) is
begin
Put_Line ("Merhaba " & Str);
end Selamlama;

-- Metot tanımı
function Main return Integer is
begin
Selamlama ("yazılımcı arkadaş");
return 1;
end Main;

-- Derleme Hatası:
-- error: end of file expected, file can have only one compilation unit

Derleyicinin verdiği hatadan da anlaşılacağı gibi böyle bir yazım geçersizdir! Bu hata, Selamlama metodunu Main fonksiyonu içerisine öğe olarak yazmak suretiyle çözülebilir:

with Ada.Text_IO; use Ada.Text_IO;

-- Fonksiyon tanımı
function Main return Integer is

-- Metot, fonksiyonun bir alt programı olarak tanımlanabilir
procedure Selamlama (Str : String) is
begin
Put_Line ("Merhaba " & Str);
end Selamlama;

begin
Selamlama ("yazılımcı arkadaş");
return 1;
end Main;

-- Ekran Çıktısı:
-- Merhaba yazılımcı arkadaş

Görüleceği gibi bu program aslında bir fonksiyon kullanılarak oluşturulmuş. Şimdi de aynı programı bir metot olarak tekrar yazalım:

with Ada.Text_IO; use Ada.Text_IO;

-- Metot tanımı
procedure Main is

-- Fonksiyon, metodun bir alt programı olarak tanımlanabilir
function Selamlama (Str : String) return Integer is
begin
Put_Line ("Merhaba " & Str);

return 1;
end Selamlama;

N : Integer;

begin
N := Selamlama ("yazılımcı arkadaş");
end Main;

-- Ekran Çıktısı:
-- Merhaba yazılımcı arkadaş

Bu iki örnek gösteriyor ki, çalışabilen bir program esasında bir fonksiyon olabileceği gibi aynı zamanda metot da olabilir.

Yukarıda verdiğimiz alt programlara ait sözdizimine bakarsanız, metotların ve fonksiyonların parametre alabildiklerini görürsünüz. İlk olarak söyleceğimiz şey, parametre alan bir alt programın esasında bir obje modülü olduğu yönündedir. Yani bir dosya derlendiği vakit, her ne kadar içerisinde metot veya fonksiyon barındırsa da çıktı olarak çalışabilen bir program olmak zorunda değil. Bu yüzden derleyici böyle bir obje modülü için .o uzantılı dosya üretir. Aşağıda bulunan iki kodun derlenmesi sonucuyla oluşan dosyaları karşılaştırabilirsiniz:

procedure Main is
begin
null;
end Main;

-- Derleme:
-- ❯ gnatmake main.adb
-- gcc -c main.adb
-- gnatbind -x main.ali
-- gnatlink main.ali

-- ❯ ls
-- main* main.adb main.ali main.o

Görüleceği üzere parametresiz bir metottan çalışabilir bir program üretilmiş.

procedure Main (Str : String) is
begin
null;
end Main;

-- Derleme:
-- ❯ gnatmake main.adb
-- gcc -c main.adb

-- ❯ ls
-- main.adb main.ali main.o

Ve parametre alan bir metottan ise bir obje dosyası üretilmiş. Aynı işlemleri fonksiyon için de yaparsanız aynı sonuçları elde edersiniz.

İkinci olarak söyleceğimiz şey şudur: bir alt programın parametreleri çeşitli modlara sahiptirler. Şayet siz herhangi bir mod kullanmazsanız, verdiğiniz değişken aslında bir sabit (immutable) gibi algılanacaktır. Alt programları daha okunuşlu ve etkili kılmak için aşağıdaki üç mod kullanılır:

  • in: Verilen parametre alt program tarafından okunabilir, fakat modifiye edilemez.
  • out: Verilen parametrenin değeri önemli değildir ve alt program içerisinde modifiye edilebilir. Eğer bu parametreyi modifiye etmeden önce okunmaya ve kullanmaya kalkılırsa beklenen sonuçlar elde edilemeyebilir, çünkü derleyicinin bu değişkeni nasıl yorumladığına göre sonuçlar değişkenlik gösterir. Şayet verilen parametre önce modifiye edilmiş ise, alt program süresince istenildiği gibi kullanılabilir. Ve ardından parametremiz alt programın bitiminde artık yeni bir değere sahip olur.
  • in out: Verilen parametre alt program içerisinde hem yazılıp hem de okunabilir. Yani bu parametrenin değeri alt program için önem arz ettiği gibi, şayet modifiye edilirse, alt programdan sonra yeni bir değere de sahiptir.

Kısaca toparlayacak olursak; eğer bir değişkenin alt program süresi boyunca yalnızca okunmasını istersek in modunu kullanmalıyız. Şayet bu değişkenin alt programın çağırıldığı zamanki içeriği değersizse ve modifiye edilmesine müsade etmek istersek out kullanılır. Eğer ki bir değişkenin o anki içeriği alt program için değerli ve modifiye edilmeye de açık olması istenirse in out modu kullanılmalı.

Bu modları kullanmanın diğer bir nedeni de yan etki oluşturmak. Yani alt programa verilen parametrelerin modifiye edilmesi suretiyle esasında yan etki oluşturur; ve böylece bir alt program ile birden çok değişkeni güncelleme imkanını elde etmiş oluruz. Örneğin bir fonksiyon tek değer döndürür; fakat bu modlar sayesinde, fonksiyon içerisinde birden çok parametre güncelleyerek fonksiyonun etki alanını genişletebiliriz. Ama fonksiyonlar için genel eğilim bu tür yan etkilerin mümkün olduğunca az tutulması yönündedir. Bunun yerine daha atomik fonksiyonların kullanılması tercih edilir.

Metotlar ve fonksiyonlar için yazacağımız makalelerde bu modları hem örneklendireceğiz hem de daha ayrıntılı inceleyeceğiz.

Bu makalelerde derleyici olarak GNAT kullandığımızdan ötürü, bu derleyiciye has bazı kısıtlamalara da değinmekte fayda var diye düşünüyoruz.

Ada dilinin standartlarında dosya isimleri veya uzantıları hakkında herhangi bir kısıtlama yoktur. Ancak buna karşılık GNAT derleyicisi .ads ve .adb uzantılarını kullanmak için yazılımcıyı zorlar. Benzer bir tutum da, bir dosyanın adı ile içerisinde bulunan alt programın veya modülün adının aynı olması yönündedir; ki bunun bir benzerini Java’da da görürüz. Şayet bunlara uymazsanız programınız elbette derlenecektir; fakat derleme aşamasında devamlı uyarı alırsınız. Örneğin yukarıdaki kodu test.txt adında bir dosya içerisine yazmış olsaydım aşağıdaki gibi uyarıya maruz kalacaktım:

❯ gnatmake test.txt
gcc -c -x ada test.txt
test.txt:4:11: warning: file name does not match unit name, should be "main.adb" [enabled by default]
gnatbind -x test.ali
gnatlink test.ali

Bu noktada belki şunu tavsiye edebiliriz: kullandığınız derleyicinin sahip olduğu özelliklere bir şekilde hakim olun ki en azından derleyicinin sizi ne kadar sınırlağını da bilirsiniz.

Kod Öbekleri

Kod öbekleri, program içerisinde herhangi bir yerde tanımlanıp kullanılabilen kod parçalarıdırlar. Bir kod öbeğinin sözdizimi şöyledir:

declare
-- öğeler
begin
-- içerik
end;

Döngüler için verdiğimiz Numbers örneğini kod öbeği kullanarak da yazabiliriz:

with Ada.Text_IO;

procedure Display_Numbers (A, B : Integer) is
use Ada.Text_IO;

Min : Integer := A;
Max : Integer := B;

begin

Find_Min_Max :
declare
Temp : Integer;
begin
if Min > Max then
Temp := Min;
Min := Max;
Max := Temp;
end if;
end Find_Min_Max;

for I in Min .. Max loop
Put_Line (I'Image);
end loop;

end Display_Numbers;

-- Test:
-- ❯ ./main 100 105
-- 100
-- 101
-- 102
-- 103
-- 104
-- 105

Yukarıda da görüleceği üzere, bir kod öbeği adlandırılabilir; örneğin biz burada Find_Min_Max adını kullandık.

Şimdiye kadar dikkat ettiyseniz bir alt programın öğeler kısmında her zaman o alt programın içeriğinde kullanılacak olan değişkenleri ve türleri tanımladık. Ve bu değişkenler alt program bitmediği sürece bellekte kalırlar. Kod öbeklerini kullanma ihtiyacı tam da bu noktada devreye girer. Yani bunların esas amacı programın herhangi bir yerinde gerekli görüldüğü takdirde bir değişkeni tanımlayıp, işi bittiğinde otomatik olarak bellekten silinmesini sağlamak. Bu kod öbeklerini C ve C++ dillerinde süslü parentezler ({ }) içerisinde yazılan kod alanı gibi de düşünülebilir.

Son olarak diyebiliriz ki, şayet bir kod öbeği içerisinde özel bir değişkene ihtiyaç yok ise kısaca begin — end bloğu da kullanılabilir.

Paketler

Ada programlama dili, alt programları daha iyi yönetebilmek ve program içerisinde tanımlanan modülleri daha kullanışlı hale getirmek için paket oluşturmamıza olanak sağlar. Bu sayede Ada, bir program kodunu anlam bağlamında önemli görülen alt birimlere ayırmamıza yardımcı olur. Tabii ki bu da kodun daha modüler ve düzenli olması demektir. Ek olarak diyebiliriz ki paketler, kodun organize edilmesini, bakımını kolaylaştırmasını ve tekrar kullanılabilmesini arttırırlar.

Bir paket, başlık (package header) ve gövde (package body) olmak üzere iki bölümden oluşur. Başlık kısmı .ads uzantılı dosyaya, gövde kısmı ise .adb uzantılı dosya içerisine yazılır.

Bir paketin başlık kısmına ait sözdizim şöyledir:

-- <Package_Name>.ads

package <Package_Name> is
-- Başlık bölümü:
-- paket içerisinde kullanılacak değişkenler, sabitler, tür tanımları,
-- alt programlar vb. öğeler tanımlanır

-- NOT: Dışarıdan erişimin sağlandığı alan (public)

private
-- NOT: Dışarıdan erişimin kısıtlandığı alan (private)

end <Package_Name>;

Aynı şekilde, bir paketin gövde kısmına ait sözdizim de aşağıdaki gibidir:

-- <Package_Name>.adb

package body <Package_Name> is
-- Gövde bölümü:
-- alt programlar icra edilir
end <Package_Name>;

Oluşturulan bir paketi başka bir paket veya modül içerisinde kullanabilmek için with anahtar sözcüğü yardımıyla bağlantı kurulur: with Package_Name;. Bu, aynı zamanda paketler arası bağımlılığı da göstermektedir. Bir paketin içerisinde bulunan öğelere (sabitlere, türlere, alt paketlere vs.) erişilmek istendiği vakit nokta (.) işerati kullanılır: noktanın solunda paketin adı ve sağında ise öğesi bulunur: Package_Name.Variable gibi. Ayrıca paketler kendilerine ait bir alan adını (namespace) da beraberinde getirirler.

Bu anlattıklarımızı bir örnek yardımıyla daha anlaşılır kılalım: farz edelim ki Geometri adında bir paket oluşturmak istiyoruz. Bu paket içerisinde, parametresi yarıçap olarak veriken ve dairenin alanını hesaplayan bir fonksiyon bulunuyor. Pi sayısı da dışarıdan erişime kapalı bir sabit olarak tanımlı olsun. O halde paketimimizin başlık kısmı şu şekilde olacaktır:

package Geometri is
function Dairenin_Alani (R : Float) return Float;
private
Pi : constant Float := 3.141_59;
end Geometri;

Geometri paketinin gövdesi de aşağıdaki gibidir:

package body Geometri is

-- Yarıçapı bilinen bir dairenin alanını hesaplayan fonksiyon
function Dairenin_Alani (R : Float) return Float is
begin
return Pi * R * R;
end Dairenin_Alani;

end Geometri;

Oluşturduğumuz Geometri paketinde bulunan Dairenin_Alani fonksiyonunu çağırmak ve bir dairenin alanını hesaplamak için bu paketi aşağıdaki gibi kullanıyoruz:

with Ada.Text_IO; use Ada.Text_IO;
with Geometri;

procedure Main is
begin

Put_Line (Geometri.Dairenin_Alani (10.0)'Image);

end Main;

Benzer şekilde bir paket, alt paketleri de içerisinde barındırmak suretiyle iç içe geçmiş, hiyerarşik bir yapıya da sahip olabilir. Fakat bu konuyu sonrası için saklayacağız ve daha fazla detay vermeyeceğiz, çünkü paketler çeşitli kullanım alanına sahipler.

Bitirirken

Yukarıda da bahsettiğimiz gibi, Ada dilinde modüler yapılar her defasında iki birimden oluşur. Bu sayede yazılımcı, neyin nereye yazacağını konusunda kafa karışıklığına uğramaz. Ayrıca kodun okunabilirliği de böylece artmış olur.

İlk makalemizde Ada programlama dilinin nesne yönelimli (object-oriented) programlama paradigmasını desteklediğinden bahsetmiştik. Bu paradigmaya dayalı programlama tekniği yine paketler sayesinde gerçekleştirilir. Ayrıca paketler, şablon (template) kullanımına da olanak sağlarlar.

Her ne kadar bu makalede belirtmesek de ek olarak şunu da ekleyelim; ikili birim mantığı görevler (tasks) ve korumalı (protected) veri türleri için de geçerlidir. Yani Ada’daki herhangi bir modüler yapı her zaman tanım ve içerik olarak iki alandan oluşur.

--

--