D Programlama Dili – Programlama dersleri ve D referansı
Ali Çehreli

katma: [mixin], program içine otomatik olarak kod yerleştirme
şablon: [template], derleyicinin örneğin 'türden bağımsız programlama' için kod üretme düzeneği
... bütün sözlük



İngilizce Kaynaklar


Diğer




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!
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.

Ö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)(in 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.