Modüller ve Kütüphaneler
D programlarını ve kütüphanelerini oluşturan en alt yapısal birimler modüllerdir.
D'nin modül kavramı çok basit bir temel üzerine kuruludur: Her kaynak dosya bir modüldür. Bu tanıma göre, şimdiye kadar deneme programlarımızı yazdığımız tek kaynak dosya bile bir modüldür.
Her modülün ismi, dosya isminin .d
uzantısından önceki bölümü ile aynıdır ve kaynak dosyanın en başına yazılan module
anahtar sözcüğü ile belirtilir. Örneğin, "kedi.d" isimli bir kaynak dosyanın modül ismi aşağıdaki gibi belirtilir:
module kedi; class Kedi { // ... }
Eğer modül bir pakedin parçası değilse module
satırı isteğe bağlıdır. (Paketleri biraz aşağıda göreceğiz.) Yazılmadığı zaman otomatik olarak dosyanın isminin .d
'den önceki bölümü kullanılır.
static this()
ve static ~this()
Modül düzeyinde tanımlanan static this()
ve static ~this()
, yapı ve sınıflardaki eşdeğerleri ile aynı anlamdadır:
module kedi; static this() { // ... modülün ilk işlemleri ... } static ~this() { // ... modülün son işlemleri ... }
Bu işlevler her iş parçacığında ayrı ayrı işletilir. (Çoğu program yalnızca main()
'in işlediği tek iş parçacığından oluşur.) İş parçacıklarının sayısından bağımsız olarak bütün programda tek kere işletilmesi gereken kodlar ise (örneğin, shared
ve immutable
değişkenlerin ilklenmeleri) shared static this()
ve shared static ~this()
işlevlerinde tanımlanırlar. Bunları daha sonraki Veri Paylaşarak Eş Zamanlı Programlama bölümünde göreceğiz.
Dosya ve modül isimleri
D programlarını Unicode olarak oluşturma konusunda şanslıyız; bu, hangi ortamda olursa olsun geçerlidir. Ancak, dosya sistemleri konusunda aynı serbesti bulunmaz. Örneğin, Windows işletim sistemlerinin standart dosya sistemleri dosya isimlerinde büyük/küçük harf ayrımı gözetmezken, Linux sistemlerinde büyük/küçük harfler farklıdır. Ayrıca, çoğu dosya sistemi dosya isimlerinde kullanılabilecek karakterler konusunda kısıtlamalar getirir.
O yüzden, programlarınızın taşınabilir olmaları için dosya isimlerinde yalnızca ASCII küçük harfler kullanmanızı öneririm. Örneğin, yukarıdaki Kedi
sınıfı ile birlikte kullanılacak olan bir Köpek
sınıfının modülünün dosya ismini "kopek.d" olarak seçebiliriz.
Dolayısıyla, modülün ismi de ASCII harflerden oluşur:
module kopek; // ASCII harflerden oluşan modül ismi class Köpek { // Unicode harflerden oluşan program kodu // ... }
Paketler
Modüllerin bir araya gelerek oluşturdukları yapıya paket denir. D'nin paket kavramı da çok basittir: Dosya sisteminde aynı klasörde bulunan bütün modüller aynı pakedin parçası olarak kabul edilirler. Pakedi içeren klasörün ismi de pakedin ismi haline gelir ve modül isimlerinin baş tarafını oluşturur.
Örneğin, yukarıdaki "kedi.d" ve "kopek.d" dosyalarının "hayvan" isminde bir klasörde bulunduklarını düşünürsek, modül isimlerinin başına klasör ismini yazmak, onları aynı pakedin modülleri yapmaya yeter:
module hayvan.kedi; class Kedi { // ... }
Aynı şekilde kopek
modülü için de:
module hayvan.kopek; class Köpek { // ... }
module
satırı bir pakedin parçası olan modüllerde zorunludur.
Paket isimleri dosya sistemi klasörlerine karşılık geldiğinden, iç içe klasörlerde bulunan modüllerin paket isimleri de o klasör yapısının eşdeğeridir. Örneğin, "hayvan" klasörünün altında bir de "omurgalilar" klasörü olsa, oradaki bir modülün paket ismi bu klasörü de içerir:
module hayvan.omurgalilar.kedi;
Kaynak dosyaların ne derece dallanacağı programın büyüklüğüne ve tasarımına bağlıdır. Küçük bir programın bütün dosyalarının tek bir klasörde bulunmasında bir sakınca yoktur. Öte yandan, dosyaları belirli bir düzen altına almak için klasörleri gerektiği kadar dallandırmak da mümkündür.
Modüllerin programda kullanılmaları
Şimdiye kadar çok kullandığımız import
anahtar sözcüğü, bir modülün başka bir modüle tanıtılmasını ve o modül içinde kullanılabilmesini sağlar:
import std.stdio;
import
'tan sonra yazılan modül ismi, eğer varsa paket bilgisini de içerir. Yukarıdaki koddaki std.
, standart kütüphaneyi oluşturan modüllerin std
isimli pakette bulunduklarını gösterir.
Benzer şekilde, hayvan.kedi
ve hayvan.kopek
modülleri bir "deneme.d" dosyasında şu şekilde bildirilir:
module deneme; // bu modülün ismi import hayvan.kedi; // kullandığı bir modül import hayvan.kopek; // kullandığı başka bir modül void main() { auto kedi = new Kedi(); auto köpek = new Köpek(); }
Not: Aşağıda anlatıldığı gibi, yukarıdaki programın derlenip oluşturulabilmesi için o modül dosyalarının da bağlayıcıya derleme satırında bildirilmeleri gerekir.
Birden fazla modül aynı anda eklenebilir:
import hayvan.kedi, hayvan.kopek;
Seçerek eklemek
Bir modüldeki isimlerin hepsini birden eklemek yerine içindeki isimler tek tek seçilerek eklenebilir:
import std.stdio : writeln; // ... writefln("Merhaba %s.", isim); // ← derleme HATASI
stdio
modülünden yalnızca writeln
eklenmiş olduğundan yukarıdaki kod derlenemez (writefln
eklenmemiştir).
İsimleri seçerek eklemek hepsini birden eklemekten daha iyidir çünkü isim çakışmalarının olasılığı daha azdır. Biraz aşağıda bir örneğini göreceğimiz gibi, isim çakışması iki farklı modüldeki aynı ismin eklenmesiyle oluşur.
Ek olarak, yalnızca belirtilen isimler derleneceğinden derleme sürelerinin kısalacağı da beklenebilir. Öte yandan, her kullanılan ismin ayrıca belirtilmesini gerektirdiğinden seçerek eklemek daha fazla emek gerektirir.
Kod örneklerini kısa tutmak amacıyla bu kitapta seçerek ekleme olanağından yararlanılmamaktadır.
Yerel import
satırları
Bu kitaptaki bütün import
satırlarını hep programların en başlarına yazdık:
import std.stdio; // ← en başta import std.string; // ← en başta // ... modülün geri kalanı ...
Aslında modüller herhangi başka bir satırda da eklenebilirler. Örneğin, aşağıdaki programdaki iki işlev ihtiyaç duydukları farklı modülleri kendi yerel kapsamlarında eklemekteler:
string mesajOluştur(string isim) { import std.string; string söz = format("Merhaba %s", isim); return söz; } void kullanıcıylaEtkileş() { import std.stdio; write("Lütfen isminizi girin: "); string isim = readln(); writeln(mesajOluştur(isim)); } void main() { kullanıcıylaEtkileş(); }
import
satırlarının yerel kapsamlarda bulunmaları modül kapsamında bulunmalarından daha iyidir çünkü derleyici kullanılmayan kapsamlardaki import
satırlarını derlemek zorunda kalmaz. Ek olarak, yerel olarak eklenmiş olan modüllerdeki isimler ancak eklendikleri kapsamda görünürler ve böylece isim çakışmalarının olasılığı da azalmış olur.
Daha sonra Katmalar bölümünde göreceğimiz şablon katmaları olanağında modüllerin yerel olarak eklenmeleri şarttır.
Bu kitaptaki örnekler yerel import
olanağından hemen hemen hiç yararlanmazlar çünkü bu olanak D'ye bu kitabın yazılmaya başlanmasından sonra eklenmiştir.
Modüllerin dosya sistemindeki yerleri
Modül isimleri dosya sistemindeki dosyalara bire bir karşılık geldiğinden, derleyici bir modül dosyasının nerede bulunduğunu modül ismini klasör ve dosya isimlerine dönüştürerek bulur.
Örneğin, yukarıdaki programın kullandığı iki modül, "hayvan/kedi.d" ve "hayvan/kopek.d" dosyalarıdır. Dolayısıyla, yukarıdaki dosyayı da sayarsak bu programı oluşturmak için üç modül kullanılmaktadır.
Kısa ve uzun isimler
Programda kullanılan isimler, paket ve modül bilgilerini de içeren uzun halde de yazılabilirler. Bunu Kedi
sınıfının tür ismini kısa ve uzun yazarak şöyle gösterebiliriz:
auto kedi0 = new Kedi(); auto kedi1 = new hayvan.kedi.Kedi(); // üsttekinin aynısı
Normalde uzun isimleri kullanmak gerekmez. Onları yalnızca olası karışıklıkları gidermek için kullanırız. Örneğin, iki modülde birden tanımlanmış olan bir ismi kısa olarak yazdığımızda derleyici hangi modüldekinden bahsettiğimizi anlayamaz.
Hem hayvan
modülünde hem de arabalar
modülünde bulunabilecek Jaguar
isimli iki sınıftan hangisinden bahsettiğimizi uzun ismiyle şöyle belirtmek zorunda kalırız:
import hayvan.jaguar; import arabalar.jaguar; // ... auto karışıklık = Jaguar(); // ← derleme HATASI auto hayvanım = hayvan.jaguar.Jaguar(); // ← derlenir auto arabam = arabalar.jaguar.Jaguar(); // ← derlenir
Takma isimle eklemek
Modüller kolaylık veya isim çakışmalarını önleme gibi amaçlarla takma isim vererek eklenebilirler:
import etobur = hayvan.jaguar; import araç = arabalar.jaguar; // ... auto hayvanım = etobur.Jaguar(); // ← derlenir auto arabam = araç.Jaguar(); // ← derlenir
Bütün modüle takma isim vermek yerine seçilen her isme ayrı ayrı takma isim de verilebilir.
Bir örnek olarak, aşağıdaki kod -w
derleyici seçeneği ile derlendiğinde derleyici .sort
niteliğinin değil, sort()
işlevinin yeğlenmesi yönünde bir uyarı verir:
import std.stdio; import std.algorithm; // ... auto dizi = [ 2, 10, 1, 5 ]; dizi.sort; // ← derleme UYARISI writeln(dizi);
Warning: use std.algorithm.sort instead of .sort property
Not: Yukarıdaki dizi.sort
ifadesi sort(dizi)
çağrısının eşdeğiridir. Farkı, ilerideki bir bölümde göreceğimiz UFCS söz dizimi ile yazılmış olmasıdır.
Bu durumda bir çözüm, std.algorithm.sort
işlevinin takma isimle eklenmesidir. Aşağıdaki yeni algSort
ismi sort()
işlevi anlamına geldiğinden derleyici uyarısına gerek kalmamış olur:
import std.stdio; import std.algorithm : algSort = sort; void main() { auto arr = [ 2, 10, 1, 5 ]; arr.algSort; writeln(arr); }
Pakedi modül olarak eklemek
Bazen bir paketteki bir modül eklendiğinde o pakedin başka modüllerinin de eklenmeleri gerekiyor olabilir. Örneğin, hayvan.kedi
modülü eklendiğinde hayvan.kopek
, hayvan.at
, vs. modülleri de ekleniyordur.
Böyle durumlarda modülleri tek tek eklemek yerine bütün pakedi veya bir bölümünü eklemek mümkündür:
import hayvan; // ← bütün paket modül gibi ekleniyor
Bu, ismi package.d
olan özel bir ayar dosyası ile sağlanır. Bu dosyada önce bir module
satırıyla pakedin ismi bildirilir, sonra da bir arada eklenmeleri gereken modüller public
olarak eklenirler:
// hayvan/package.d dosyasının içeriği: module hayvan; public import hayvan.kedi; public import hayvan.kopek; public import hayvan.at; // ... diğer modüller için de benzer satırlar ...
Bir modülün public
olarak eklenmesi, kullanıcıların eklenen modüldeki isimleri görebilmelerini sağlar. Sonuç olarak, kullanıcılar aslında bir paket olan hayvan
modülünü eklediklerinde hayvan.kedi
, hayvan.kopek
, vs. modüllere otomatik olarak erişmiş olurlar.
Modül olanaklarını emekliye ayırmak
Modüller geliştikçe yeni sürümleri kullanıma sunulur. Modülün yazarları bazı olanakların belirli bir sürümden sonra emekliye ayrılmalarına karar vermiş olabilirler. Bir olanağın emekliye ayrılması, yeni yazılacak olan programların artık o olanağı kullanmamaları gerektiği anlamına gelir. Emekliye ayrılan bir olanak daha ilerideki bir sürümde modülden çıkartılabilir bile.
Olanakların emekliye ayrılmalarını gerektiren çeşitli nedenler vardır. Örneğin, modülün yeni sürümü o olanağın yerine kullanılabilecek daha iyi bir olanak getiriyordur, olanak başka bir modüle taşınmıştır, olanağın ismi modülün geri kalanıyla uyumlu olsun diye değiştirilmiştir, vs.
Bir olanağın emekliye ayrılmış olduğu deprecated
anahtar sözcüğü ile ve gerekiyorsa özel bir mesajla bildirilir. Örneğin, aşağıdaki mesaj, bir_şey_yap()
işlevini kullananlara işlevin isminin değiştiğini belirtmektedir:
deprecated("Lütfen bunun yerine birŞeyYap() işlevini kullanınız.") void bir_şey_yap() { // ... }
Emekliye ayrılan olanak kullanıldığında derleyicinin nasıl davranacağı aşağıdaki derleyici seçenekleri ile ayarlanabilir:
-
-d
: Emekliye ayrılmış olan olanakların kullanılmasına izin verilir -
-dw
: Emekliye ayrılmış olan olanak kullanıldığında derleme uyarısı verilir -
-de
: Emekliye ayrılmış olan olanak kullanıldığında derleme hatası verilir
Örneğin, emekliye ayrılmış olan yukarıdaki olanağı kullanan bir program -de
seçeneği ile derlendiğinde derleme hatası oluşur:
bir_şey_yap();
$ dmd deneme.d -de deneme.d: Deprecation: function deneme.bir_şey_yap is deprecated - Lütfen bunun yerine birŞeyYap() işlevini kullanınız.
Çoğu durumda, emekliye ayrılan olanak yeni olanağın takma ismi olarak tanımlanır:
deprecated("Lütfen bunun yerine birŞeyYap() işlevini kullanınız.") alias bir_şey_yap = birŞeyYap; void birŞeyYap() { // ... }
alias
anahtar sözcüğünü ilerideki bir bölümde göreceğiz.
Modüllerdeki tanımların programa dahil edilmesi
import
anahtar sözcüğü, belirtilen modülün programın parçası haline gelmesi için yeterli değildir. import
, yalnızca o modüldeki olanakların bu kaynak kod içinde kullanılabilmelerini sağlar. O kadarı ancak kaynak kodun derlenebilmesi için yeterlidir.
Yukarıdaki programı yalnızca "deneme.d" dosyasını kullanarak oluşturmaya çalışmak yetmez:
$ dmd deneme.d -w -de
deneme.o: In function `_Dmain':
deneme.d: undefined reference to `_D6hayvan4kedi4Kedi7__ClassZ'
deneme.d: undefined reference to `_D6hayvan5kopek6Köpek7__ClassZ'
collect2: ld returned 1 exit status
O hata mesajları bağlayıcıdan gelir. Her ne kadar anlaşılmaz isimler içeriyor olsalar da, yukarıdaki hata mesajları programda kullanılan bazı tanımların bulunamadıklarını bildirir.
Programın oluşturulması, perde arkasında çağrılan bağlayıcının görevidir. Derleyici, derlediği modülleri bağlayıcıya verir; program, bağlayıcının bir araya getirdiği parçalardan oluşturulur.
O yüzden, programı oluşturan bütün parçaların derleme satırında belirtilmeleri gerekir. Yukarıdaki programın oluşturulabilmesi için, kullandığı "hayvan/kedi.d" ve "hayvan/kopek.d" dosyaları da derleme satırında bildirilmelidir:
$ dmd deneme.d hayvan/kedi.d hayvan/kopek.d -w -de
Modülleri derleme satırında her program için ayrı ayrı belirtmek yerine kütüphaneler içinden de kullanabiliriz.
Kütüphaneler
Modül tanımlarının derlendikten sonra bir araya getirilmelerine kütüphane adı verilir. Kütüphaneler kendileri program olmadıklarından, programların başlangıç işlevi olan main
kütüphanelerde bulunmaz. Kütüphaneler yalnızca işlev, yapı, sınıf, vs. tanımlarını bir araya getirirler. Daha sonra program oluşturulurken programın diğer modülleriyle bağlanırlar.
Kütüphane oluşturmak için dmd'nin -lib
seçeneği kullanılır. Oluşturulan kütüphanenin isminin hayvan
olacağını da -of
seçeneği ile bildirirsek, yukarıdaki "kedi.d" ve "kopek.d" modüllerini içeren bir kütüphane şu şekilde oluşturulabilir:
$ dmd hayvan/kedi.d hayvan/kopek.d -lib -ofhayvan -w -de
Konsoldan çalıştırılan o komut, belirtilen .d
dosyalarını derler ve bir kütüphane dosyası olarak bir araya getirir. Çalıştığınız ortama bağlı olarak kütüphane dosyasının ismi farklı olacaktır. Örneğin, Linux ortamlarında kütüphane dosyalarının uzantıları .a olur: hayvan.a
.
Program oluşturulurken artık "hayvan/kedi.d"nin ve "hayvan/kopek.d"nin ayrı ayrı bildirilmelerine gerek kalmaz. Onları içeren kütüphane dosyası tek başına yeterlidir:
$ dmd deneme.d hayvan.a -w -de
O komut, daha önce kullandığımız şu komutun eşdeğeridir:
$ dmd deneme.d hayvan/kedi.d hayvan/kopek.d -w -de
Bir istisna olarak, şimdiye kadar çok yararlandığımız Phobos modüllerini içeren standart kütüphanenin açıkça bildirilmesi gerekmez. O kütüphane, programa otomatik olarak dahil edilir. Yoksa normalde onu da örneğin şu şekilde belirtmemiz gerekirdi:
$ dmd deneme.d hayvan.a /usr/lib64/libphobos2.a -w -de
Not: Phobos kütüphane dosyasının yeri ve ismi sizin ortamınızda farklı olabilir.
Başka dillerin kütüphanelerini kullanmak
C ve C++ gibi başka bazı derlemeli dillerin kütüphaneleri D programlarında kullanılabilir. Ancak, farklı diller farklı bağlanım kullandıklarından, böyle bir kütüphanenin D ile kullanılabilmesi için o kütüphanenin bir D ilintisinin olması gerekir.
Bağlanım, bir kütüphanenin olanaklarının dışarıdan erişimini ve o olanakların isimlerinin (sembollerinin) derlenmiş kodda nasıl ifade edildiklerini belirleyen kurallar bütünüdür. Derlenmiş koddaki isimler programcının kaynak kodda yazdığı isimlerden farklıdır: Derlenmiş koddaki isimler belirli bir dilin veya bir derleyicinin kurallarına göre özgünleştirilmişlerdir.
Örneğin, ismi kaynak kodda foo
olan bir işlevin derlenmiş koddaki özgün ismi, C bağlanım kurallarına göre başına alt çizgi karakteri eklenerek oluşturulur: _foo
. Özgün isim üretme C++ ve D dillerinde daha karmaşıktır çünkü bu diller aynı ismin farklı modüllerde, yapılarda, sınıflarda, ve bir işlevin farklı yüklemelerinde kullanılmasına izin verir. D kaynak kodundaki foo
gibi bir işlevin özgün ismi onu olası bütün başka foo
isimlerinden ayırt edecek biçimde seçilir. Özgün isimlerin tam olarak ne oldukları genelde programcı için önemli olmasa da, bu konuda core.demangle
modülünden yararlanılabilir:
module deneme; import std.stdio; import core.demangle; void foo() { } void main() { writeln(mangle!(typeof(foo))("deneme.foo")); }
Not: Söz dizimi kitabın bu noktasında yabancı gelen mangle
bir işlev şablonudur. Şablonları daha sonra Şablonlar bölümünde göreceğiz.
Programın çıktısı, yukarıdaki foo
ile aynı türden olan deneme.foo
isimli bir işlevin özgün ismini göstermektedir:
_D6deneme3fooFZv
Bağlayıcının verdiği hata mesajlarının anlaşılmaz isimler içermelerinin nedeni de özgün isimlerdir. Örneğin, yukarıdaki bir bağlayıcı hata mesajında hayvan.kedi.Kedi
ismi değil, _D6hayvan4kedi4Kedi7__ClassZ
ismi geçmiştir.
extern()
niteliği olanakların bağlanımlarını belirtmek için kullanılır. extern()
ile kullanılabilen bağlanım türleri şunlardır: C
, C++
, D
, Objective-C
, Pascal
, System
, ve Windows
. Örneğin, bir C kütüphanesinde tanımlanmış olan bir işlevi çağırması gereken bir D kodunun o işlevi C bağlanımı ile bildirmesi gerekir:
// 'foo'nun C bağlanımı olduğu bildiriliyor (örneğin, bir C // kütüphanesinde tanımlanmıştır) extern(C) void foo(); void main() { foo(); // bu işlev çağrısı '_foo' özgün ismi ile yapılır }
C++'ın namespace
anahtar sözcüğü ile tanımlanan isim alanları extern(C++)
'ın ikinci parametresi olarak belirtilir. Örneğin, aşağıdaki bar()
bildirimi, C++ kütüphanesindeki a::b::c::bar()
işlevine karşılık gelir (dikkat ederseniz, D kodu ::
yerine nokta kullanır):
// 'bar'ın a::b::c isim alanında bulunduğu ve C++ bağlanımı // olduğu bildiriliyor: extern(C++, a.b.c) void bar(); void main() { bar(); // a::b::c::bar()'a çağrıdır a.b.c.bar(); // üsttekinin eşdeğeri }
Bir kütüphanedeki olanakların D bildirimlerini içeren dosyaya o kütüphanenin D ilintisi denir. D ilintilerini elle kendiniz yazmak yerine, çoğu yaygın kütüphanenin ilintilerini içeren Deimos projesinden yararlanmanızı öneririm.
Bağlanım türü belirtilmeden kullanılan extern
niteliğinin farklı bir anlamı vardır: Bir değişken için kullanılan yerin başka bir kütüphanenin sorumluluğunda olduğunu bildirir. Farklı anlamlar taşıdıklarından, extern
ve extern()
nitelikleri birlikte kullanılabilir:
// 'g_degisken' için kullanılan yerin bir C kütüphanesi // tarafından zaten ayrıldığı bildiriliyor: extern(C) extern int g_degisken;
Yukarıdaki extern
niteliği kullanılmasa, C bağlanımına sahip olmasından bağımsız olarak, g_degisken
bu D modülünün bir değişkeni haline gelirdi.