Katmalar
Katmalar, derleme zamanında şablonlar veya dizgiler tarafından üretilen kodların programın istenen noktalarına eklenmelerini sağlarlar.
Şablon katmaları
Şablonların belirli kalıplara göre kod üreten olanaklar olduklarını Şablonlar ve Ayrıntılı Şablonlar bölümlerinde görmüştük. Şablonlardan yararlanarak farklı parametre değerleri için işlev, yapı, birlik, sınıf, arayüz, ve yasal olduğu sürece her tür D kodunu oluşturabiliyorduk.
Şablon katmaları, bir şablon içinde tanımlanmış olan bütün kodların programın belirli bir noktasına, sanki oraya açıkça elle yazılmış gibi eklenmelerini sağlarlar. Bu açıdan, C ve C++ dillerindeki makrolar gibi işledikleri düşünülebilir.
mixin
anahtar sözcüğü, şablonun belirli bir kullanımını programın herhangi bir noktasına yerleştirir. "Katmak", "içine karıştırmak" anlamına gelen "mix in"den türemiştir. mixin
anahtar sözcüğünden sonra şablonun belirli parametre değerleri için bir kullanımı yazılır:
mixin bir_şablon!(şablon_parametreleri)
O şablonun o parametrelerle kullanımı için üretilen kodlar, oldukları gibi mixin
satırının bulunduğu noktaya yerleştirilirler. Aşağıdaki örnekte göreceğimiz gibi, mixin
anahtar sözcüğü şablon katmalarının tanımlarında da kullanılır.
Örnek olarak bir köşe dizisini ve o köşeler üzerindeki işlemleri kapsayan bir şablon düşünelim:
mixin template KöşeDizisiOlanağı(T, size_t adet) { T[adet] köşeler; void köşeDeğiştir(size_t indeks, T köşe) { köşeler[indeks] = köşe; } void köşeleriGöster() { writeln("Bütün köşelerim:"); foreach (i, köşe; köşeler) { writef("%s:%s ", i, köşe); } writeln(); } }
O şablon, dizi elemanlarının türü ve eleman adedi konusunda esneklik getirmektedir; tür ve eleman adedi ihtiyaca göre serbestçe seçilebilir.
O şablonun int
ve 2
parametreleri ile kullanımının istenmekte olduğu, bir mixin
ile şöyle belirtilir:
mixin KöşeDizisiOlanağı!(int, 2);
Yukarıdaki mixin
, şablonun içindeki kodları kullanarak iki elemanlı int
dizisini ve o diziyi kullanan iki işlevi oluşturur. Böylece, onları örneğin bir yapının üyeleri haline getirebiliriz:
struct Çizgi { mixin KöşeDizisiOlanağı!(int, 2); }
Şablon içindeki kodlar, T
'ye karşılık int
, ve adet
'e karşılık 2
olacak şekilde üretilirler ve mixin
anahtar sözcüğünün bulunduğu yere yerleştirilirler. Böylece, Çizgi
yapısı 2 elemanlı bir dizi ve o dizi ile işleyen iki işlev edinmiş olur:
import std.stdio; void main() { auto çizgi = Çizgi(); çizgi.köşeDeğiştir(0, 100); çizgi.köşeDeğiştir(1, 200); çizgi.köşeleriGöster(); }
Program şu çıktıyı üretir:
Bütün köşelerim: 0:100 1:200
Aynı şablonu örneğin bir işlev içinde ve başka parametre değerleri ile de kullanabiliriz:
struct Nokta { int x; int y; } void main() { mixin KöşeDizisiOlanağı!(Nokta, 5); köşeDeğiştir(3, Nokta(3, 3)); köşeleriGöster(); }
O mixin
, main
'in içine yerel bir dizi ve yerel iki işlev yerleştirir. Çıktısı:
Bütün köşelerim: 0:Nokta(0,0) 1:Nokta(0,0) 2:Nokta(0,0) 3:Nokta(3,3) 4:Nokta(0,0)
Şablon katmaları yerel import
kullanmalıdır
Şablon katmalarının oldukları gibi kod içine yerleştirilmeleri kendi kullandıkları modüller açısından bir sorun oluşturur: Şablonun kendi yararlandığı modüller şablonun sonradan eklendiği noktalarda mevcut olmayabilirler.
Örneğin, aşağıdaki şablonun a
isimli bir modülde tanımlı olduğunu düşünelim. Doğal olarak, bu şablon yararlanmakta olduğu format()
'ın tanımlandığı std.string
modülünü ekleyecektir:
module a; import std.string; // ← yanlış yerde mixin template A(T) { string a() { T[] dizi; // ... return format("%(%s, %)", dizi); } }
Ancak, std.string
modülü o şablonu kullanan ortamda eklenmiş değilse format()
'ın tanımının bilinmediği yönünde bir derleme hatası alınır. Örneğin, a
modülünü kullanan aşağıdaki program derlenemez:
import a; void main() { mixin A!int; // ← derleme HATASI }
Error: undefined identifier format
Error: mixin deneme.main.A!int error instantiating
O yüzden, şablon katmalarının kullandıkları modüller yerel kapsamlarda eklenmelidirler:
module a; mixin template A(T) { string a() { import std.string; // ← doğru yerde T[] dizi; // ... return format("%(%s, %)", dizi); } }
Şablon tanımının içinde olduğu sürece, import
bildirimi a()
işlevinin dışında da bulunabilir.
Sarmalayan türü katmanın içinde edinmek
Bazı durumlarda katmanın kendisi içine katıldığı türü edinmek zorunda kalabilir. Bunun için daha önce Ayrıntılı Şablonlar bölümünde gördüğümüz this
şablon parametrelerinden yararlanılır:
mixin template ŞablonKatması(T) { void birİşlev(this AsılTür)() { import std.stdio; writefln("İçine katıldığım asıl tür: %s", AsılTür.stringof); } } struct BirYapı { mixin ŞablonKatması!(int); } void main() { auto a = BirYapı(); a.birİşlev(); }
Çıktısı, katılan işlevin asıl türü BirYapı
olarak edindiğini gösteriyor:
İçine katıldığım asıl tür: BirYapı
Dizgi katmaları
D'nin güçlü bir olanağı değerleri derleme sırasında bilinen dizgilerin de kod olarak programın içine yerleştirilebilmeleridir.
İçinde yasal D kodları bulunan her dizgi mixin
anahtar sözcüğü ile programa eklenebilir. Bu kullanımda dizginin parantez içinde belirtilmesi gerekir:
mixin (derleme_zamanında_oluşturulan_dizgi)
Örneğin, merhaba dünya programını bir dizgi katması ile şöyle yazabiliriz:
import std.stdio; void main() { mixin (`writeln("Merhaba, Dünya!");`); }
Dizgi içindeki kod mixin
satırına eklenir, program derlenir, ve beklediğimiz çıktıyı verir:
Merhaba, Dünya!
Bunun etkisini göstermek için biraz daha ileri gidebilir ve bütün programı bile bir dizgi katması olarak yazabiliriz:
mixin ( `import std.stdio; void main() { writeln("Merhaba, Dünya!"); }` );
Bu örneklerdeki mixin
'lere gerek olmadığı açıktır. O kodların şimdiye kadar hep yaptığımız gibi programa açıkça yazılmaları daha mantıklı olacaktır.
Dizgi katmalarının gücü, kodun derleme zamanında otomatik olarak oluşturulabilmesinden gelir. Derleme zamanında oluşturulabildiği sürece, mixin
ifadesi işlevlerin döndürdüğü dizgilerden bile yararlanabilir. Aşağıdaki örnek mixin
'e verilecek olan kod dizgilerini CTFE'den yararlanarak bir işleve oluşturtmaktadır:
import std.stdio; string yazdırmaDeyimi(string mesaj) { return `writeln("` ~ mesaj ~ `");`; } void main() { mixin (yazdırmaDeyimi("Merhaba, Dünya!")); mixin (yazdırmaDeyimi("Selam, Dünya!")); }
Yukarıdaki program, yazdırmaDeyimi
'nin oluşturduğu iki dizgiyi mixin
satırlarının yerlerine yerleştirir ve program o kodlarla derlenir. Burada dikkatinizi çekmek istediğim nokta, writeln
işlevlerinin yazdırmaDeyimi
'nin içinde çağrılmadıklarıdır. yazdırmaDeyimi
'nin yaptığı, yalnızca içinde "writeln"
geçen dizgiler döndürmektir.
O dizgiler mixin
'lerin bulundukları satırlara kod olarak yerleştirilirler. Sonuçta derlenen program, şunun eşdeğeridir:
import std.stdio; void main() { writeln("Merhaba, Dünya!"); writeln("Selam, Dünya!"); }
mixin
'li program, sanki o iki writeln
satırı varmış gibi derlenir ve çalışır:
Merhaba, Dünya! Selam, Dünya!
Birden fazla mixin
parametre değeri
mixin
birden fazla parametre değeri aldığında onların string
karşılıklarını otomatik olarak birleştirir:
mixin ("const a = ", int.sizeof, ";");
Bu da bazı durumlarda örneğin format
ifadeleri kullanmaktan daha kullanışlı ve okunaklı olabilir:
mixin (format!"const a = %s;"(int.sizeof)); // Üsttekinin eşdeğeri
Dizgi katmalarındaki kod hataları
Üretilen kodun bütünü kaynak kod içinde açıkça görülmediğinden, mixin
ifadelerindeki derleme hatalarının nedenleri kolayca anlaşılmayabilir. dmd
'nin -mixin
komut satırı seçeneği, dizgi katmalarını belirtilen dosyaya yazar ve böylece hata ayıklamada yardımcı olur.
Katmaya çalıştığı kodda bir yazım hatası bulunan aşağıdaki programa bakalım. Ürettiği koddaki yapı üyesinin tanımının sonundaki noktalı virgülün eksik olduğu, mixin
satırına işaret eden derleme hatasından anlaşılamamaktadır:
string structÜret(string yapıİsmi, string üyeİsmi) { import std.format; return format!"struct %s {\n int %s\n}"(yapıİsmi, üyeİsmi); } mixin (structÜret("S", "a")); // ← derleme HATASI void main() { }
Program -mixin
derleyici seçeneği ile derlendiğinde, derleme hatası, belirtilen mixin_kodlari
dosyasının içindeki bir satıra işaret eder:
$ dmd -mixin=mixin_kodlari deneme.d
mixin_kodlari(154): Error: semicolon expected, not }
Standart kütüphane tarafından mixin
ile katılmış olan kodlara ek olarak mixin_kodlari
dosyası içinde aşağıdaki kod da görülecektir:
[...]
// expansion at deneme.d(6)
struct S {
int a
} ← Satır 154
Dizgi katmalarındaki hataları ayıklama konusunda bir diğer olanak, üretilen kodu derleme sırasında çıktıya yazdıran pragma(msg)
'dir. mixin
anahtar sözcüğünü geçici olarak pragma(msg)
ile değiştirmeyi gerektirdiğinden bu yöntemin daha az kullanışlı olduğu düşünülebilir:
pragma(msg, structÜret("S", "a"));
Katmaların isim alanları
Şablon katmaları isim çakışmalarını önlemeye yönelik korumalar getirirler.
Örneğin, aşağıdaki programda main()
'in kapsamı içinde iki farklı i
tanımı bulunmaktadır: Biri main()
içinde açıkça tanımlanan, diğeri de Şablon
'un kod içine katılması ile gelen. Şablon katması sonucunda oluşan isim çakışmaları durumunda şablonun getirdiği tanım değil, katmayı kapsayan isim alanındaki tanım kullanılır:
import std.stdio; template Şablon() { int i; void yazdır() { writeln(i); // Her zaman için Şablon içindeki i'dir } } void main() { int i; mixin Şablon; i = 42; // main içindeki i'yi değiştirir writeln(i); // main içindeki i'yi yazdırır yazdır(); // Şablon'un getirdiği i'yi yazdırır }
Yukarıdaki açıklama satırlarından da anlaşılacağı gibi, her şablon katması kendi içeriğini sarmalayan bir isim alanı tanımlar ve şablon içindeki kodlar öncelikle o isim alanındaki isimleri kullanırlar. Bunu yazdır()
'ın davranışında görüyoruz:
42
0 ← yazdır()'ın yazdırdığı
Birden fazla şablonun aynı ismi tanımlaması ise derleyicinin kendi başına karar veremeyeceği bir isim çakışmasıdır. Bunu görmek için aynı şablonu iki kere katmayı deneyelim:
template Şablon() { int i; } void main() { mixin Şablon; mixin Şablon; i = 42; // ← derleme HATASI }
Derleme hatası hangi i
'den bahsedildiğinin bilinemediğini bildirir:
Error: deneme.main.Şablon!().i at ... conflicts with
deneme.main.Şablon!().i at ...
Bu gibi isim çakışmalarını gidermenin yolu şablon katmalarına koda eklendikleri noktada isim alanı atamak ve onların içerdikleri isimleri bu isim alanları ile kullanmaktır:
mixin Şablon A; // A.i'yi tanımlar mixin Şablon B; // B.i'yi tanımlar A.i = 42; // ← hangi i olduğu bellidir
Bu olanaklar dizgi katmalarında bulunmaz. Buna rağmen, bütün işi verilen bir dizgiyi şablon katması haline getiren bir şablondan yararlanarak bunun da üstesinden gelinebilir.
Bunu görmek için önce yukarıdaki isim çakışması sorununu bu sefer de bir dizgi katması ile yaşayalım:
void main() { mixin ("int i;"); mixin ("int i;"); // ← derleme HATASI i = 42; }
Bu durumdaki derleme hatası i
'nin zaten tanımlanmış olduğunu bildirir:
Error: declaration deneme.main.i is already defined
Bu sorunu gidermenin bir yolu dizgi katmasını şablon katmasına dönüştüren aşağıdaki gibi basit bir şablon kullanmaktır:
template ŞablonKatmasıOlarak(string dizgi) { mixin (dizgi); } void main() { mixin ŞablonKatmasıOlarak!("int i;") A; // A.i'yi tanımlar mixin ŞablonKatmasıOlarak!("int i;") B; // B.i'yi tanımlar A.i = 42; // ← hangi i olduğu bellidir }
İşleç yüklemedeki kullanımı
Bazı işleçlerin şablon söz dizimi ile tanımlandıklarını İşleç Yükleme bölümünde görmüştük. O söz dizimlerini o bölümde bir kalıp olarak kabul etmenizi rica etmiş ve onların şablonlarla ilgili bölümlerden sonra açıklığa kavuşacaklarını söylemiştim.
İşleç yüklemeyle ilgili olan üye işlevlerin şablonlar olarak tanımlanmalarının nedeni, işleçleri belirleyen şablon parametrelerinin string
türünde olmaları ve bu yüzden dizgi katmalarından yararlanabilmeleridir. Bunun örneklerini hem o bölümde hem de o bölümün problem çözümlerinde görmüştük.
Eklenen sonlandırıcılar
Kullanıcı türlerine mixin
ile birden fazla sonlandırıcı işlev eklenebilir. Nesne sonlanırken bu sonlandırıcılar eklendikleri sıranın tersi sırada işletilirler. Bu olanak kullanıcı türlerine birden fazla kaynak eklenmesini ve her kaynağın temizlik kodunun kendisi tarafından getirilmesini sağlar.
import std.stdio; mixin template Foo() { ~this() { writeln("Foo tarafından eklenen sonlandırıcı"); } } mixin template Bar() { ~this() { writeln("Bar tarafından eklenen sonlandırıcı"); } } struct S { ~this() { writeln("Türün kendi sonlandırıcısı"); } mixin Foo; mixin Bar; } void main() { auto s = S(); }
Bar tarafından eklenen sonlandırıcı Foo tarafından eklenen sonlandırıcı Türün kendi sonlandırıcısı
Bu bölümün yazıldığı sırada halen etkili olan bir hata nedeniyle bu olanak kurucu işlev gibi diğer özel işlevlerle kullanılamaz. Ek olarak, bir dizgi katması tarafından eklenen sonlandırıcı işlev, türün kendi sonlandırıcısı ile çakışır.
Dosya içeriğinin programa dahil edilmesi
Dosyalar derleme zamanında okunabilir ve içerikleri oldukları gibi programa dahil edilebilir. Dosyanın içeriği string
olarak işlem görür ve bir dizginin kullanılabildiği her yerde kullanılabilir. Örneğin, mixin
ile program kodu olarak dahil edilebilir.
Örneğin, dosya_bir
ve dosya_iki
adındaki iki dosyanın içerikleri aşağıdaki gibi olsun.
dosya_bir
:Merhaba
dosya_iki
:s ~= ", Dünya!"; import std.stdio; writeln(s);
Bu durumda, aşağıdaki programdaki iki import
anahtar sözcüğü, derleme zamanında o dosya içeriklerinden oluşan iki dizgi yerine geçer:
void main() { string s = import ("dosya_bir"); mixin (import ("dosya_iki")); }
Bu olanak, dosyaların bulundukları klasörlerin -J
derleyici seçeneği ile bildirilmelerini gerektirir. Örneğin, o iki dosya Linux ortamlarında .
ile belirtilen bu klasör içinde iseler, program aşağıdaki komutla derlenebilir:
$ dmd -J. deneme.d
Çıktısı:
Merhaba, Dünya!
Dosya içerikleri dizgi olarak düşünüldüğünde program aşağıdakinin eşdeğeridir:
void main() { string s = `Merhaba`; mixin (`s ~= ", Dünya!"; import std.stdio; writeln(s);`); }
mixin
ile kullanılan dizginin kod olarak dahil edildiğini de göz önüne alınca programın aşağıdakinin eşdeğeri olduğu görülür:
void main() { string s = `Merhaba`; s ~= ", Dünya!"; import std.stdio; writeln(s); }
Örnek
(Not: Kıstasların bu örnekte olduğu gibi dizgi olarak belirtilmeleri isimsiz işlevlerin =>
söz dizimlerinden daha eski bir olanaktır. Bu örnekteki dizgi kullanımı Phobos'ta hâlâ geçerli olsa da =>
söz dizimi daha kullanışlıdır.)
Kendisine verilen sayılardan belirli bir koşula uyanlarını seçen ve bir dizi olarak döndüren bir işlev şablonuna bakalım:
int[] seç(string koşul)(int[] sayılar) { int[] sonuç; foreach (eleman; sayılar) { if (mixin (koşul)) { sonuç ~= eleman; } } return sonuç; }
O işlev şablonu seçme koşulunu şablon parametresi olarak almakta ve if
deyiminin parantezinin içine o koşulu olduğu gibi kod olarak yerleştirmektedir.
O ifadenin örneğin elemanların 7'den küçük olanlarını seçmesi için if
deyimi içine şöyle bir ifadenin yazılması gerekir:
if (eleman < 7) {
Yukarıdaki seç
şablonu bize o koşulu programda bir dizgi olarak bildirme olanağı vermiş olur:
int[] sayılar = [ 1, 8, 6, -2, 10 ]; int[] seçilenler = seç!"eleman < 7"(sayılar);
Önemli bir ayrıntı olarak, seç
şablonuna parametre olarak verilen dizginin içinde kullanılan değişken isminin seç
işlevi içinde tanımlanan değişken ismi ile aynı olması şarttır ve o değişken isminin ne olduğu seç
işlevinin belgelerinde belirtilmek zorundadır. O işlevi kullanan programcılar da o isme uymak zorundadırlar.
Bu amaçla kullanılan değişken isimleri konusunda Phobos'ta bir standart gelişmeye başlamıştır. Benim seçtiğim "eleman" gibi uzun bir isim değil; a, b, n diye tek harflik isimler kullanılır.