D.ershane D Programlama Dili Dersleri

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

Bölümler
İngilizce Kaynaklar
Diğer



Katmalar

Katmalar, derleme zamanında şablonlar veya dizgiler tarafından üretilen kodların programın istenen noktalarına eklenmelerine yararlar.

Şablon katmaları

Şablonların belirli kalıplara göre kod üreten olanaklar olduklarını Şablonlar ve Ayrıntılı Şablonlar derslerinde görmüştük. Şablonlardan yararlanarak farklı parametre değerleri için işlev, yapı, birlik, sınıf, arayüz, veya 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.

Örnek olarak bir köşe dizisini, ve o köşeler üzerindeki işlemleri tanımlayan bir şablon düşünelim:

template KöşeDizisiOlanağı(T, int adet)
{
    T[adet] köşeler;

    void köşeDeğiştir(int 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) {
            write(i, ":", köşe, ' ');
        }

        writeln();
    }
}

O şablon, dizi elemanlarının türü ve eleman adedi konusunda esneklik getirmektedir. Tür ve eleman adedi, kullanıma göre artık serbestçe seçilebilir.

O şablonun int türüyle ve 2 değeriyle kullanılmasını istediğimizi bir mixin'e şöyle belirtebiliriz:

    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ının 2 elemanlı bir dizisi ve o dizi ile işleyen iki işlevi olmuş olur:

    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:

void main()
{
    mixin KöşeDizisiOlanağı!(Nokta, 5);

    köşeDeğiştir(3, Nokta(1.1, 2.2));
    köşeleriGöster();
}

O mixin, main'in içine yerel bir dizi ve yerel iki işlev yerleştirir. mixin'e verilen Nokta'nın da şöyle basit bir yapı olduğunu düşünebiliriz:

import std.string;

struct Nokta
{
    double x = 0;
    double y = 0;

    string toString() const
    {
        return format("(%s,%s)", x, y);
    }
}

Çıktısı:

Bütün köşelerim:
0:(0,0) 1:(0,0) 2:(0,0) 3:(1.1,2.2) 4:(0,0) 
Dizgi katmaları

D'nin çok güçlü bir olanağı, değerleri derleme sırasında bilinebilen dizgilerin 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"); }`
);

Aynı çıktı elde edilir:

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ıdı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 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
İşleç yüklemedeki kullanımı

İşleç Yükleme dersinde işleçlerin şablon söz dizimi ile tanımlandıklarını görmüştük. O söz dizimlerini bir kalıp olarak kabul etmenizi rica etmiş ve onların şablonlarla ilgili derslerden 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.

Bunu görmek için tekrar Süre yapısına bakalım. Bu türün nesnelerinin + ve - işleçleriyle kullanılırken int kabul edebilmeleri için aşağıdaki üye işlevleri tanımlanmış olsun:

struct Süre
{
    int dakika;

    // süre += 2 gibi kullanımlar için
    void opOpAssign(string işleç)(int miktar)
        if (işleç == "+")
    {
        dakika += miktar;
    }

    // süre -= 2 gibi kullanımlar için
    void opOpAssign(string işleç)(int miktar)
        if (işleç == "-")
    {
        dakika -= miktar;
    }

    // süre + 2 gibi kullanımlar için
    Süre opBinary(string işleç)(int miktar)
        if (işleç == "+")
    {
        return Süre(dakika + miktar);
    }

    // süre - 2 gibi kullanımlar için
    Süre opBinary(string işleç)(int miktar)
        if (işleç == "-")
    {
        return Süre(dakika - miktar);
    }
}

Görüldüğü gibi, opOpAssign'ın ve opBinary'nin + ve - işleçleri için tanımları "+" ve "-" karakterleri dışında aynıdır. Buradaki kod tekrarını ortadan kaldırmak için mixin'lerden yararlanılabilir:

struct Süre
{
    int dakika;

    void opOpAssign(string işleç)(int miktar)
    {
        mixin("dakika " ~ işleç ~ "= miktar;");
    }

    Süre opBinary(string işleç)(int miktar)
    {
        mixin("return Süre(dakika ") ~ işleç ~ " miktar;");
    }
}

Üstelik bu sefer şablon kısıtlamaları da kullanılmadığı için başka aritmetik işlemler de desteklenmektedir:

import std.stdio;
import std.string;

struct Süre
{
    int dakika;

    void opOpAssign(string işleç)(int miktar)
    {
        mixin("dakika " ~ işleç ~ "= miktar;");
    }

    Süre opBinary(string işleç)(int miktar)
    {
        mixin("return Süre(dakika ") ~ işleç ~ " miktar;");
    }

    string toString()
    {
        return format("%s dakika", dakika);
    }
}

void main()
{
    auto süre = Süre(10);
    süre += 1;
    süre -= 2;
    süre /= 3;
    süre %= 4;

    writeln("süre  : ", süre);

    auto sonuç1 = süre + 3;
    auto sonuç2 = süre - 4;
    auto sonuç3 = süre / 4;
    auto sonuç4 = süre % 4;

    writeln("sonuç1: ", sonuç1);
    writeln("sonuç2: ", sonuç2);
    writeln("sonuç3: ", sonuç3);
    writeln("sonuç4: ", sonuç4);
}

Çıktısı:

süre  : 3 dakika
sonuç1: 6 dakika
sonuç2: -1 dakika
sonuç3: 0 dakika
sonuç4: 3 dakika

Gerektiğinde yine de şablon kısıtlamaları eklenebilir ve uygun olmayan işlemler o sayede elenebilir.

Örnek

Kendisine verilen sayılardan belirli bir koşula uyanlarını seçen ve bir dizi olarak döndüren bir işleve 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, seçme koşulunu bir ş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) {

İşte, yukarıdaki seç işlevi, bize o koşulu programda bir dizgi olarak bildirme olanağı verir:

    int[] sayılar = [ 1, 8, 6, -2, 10 ];
    int[] seçilenler = seç!"eleman < 7"(sayılar);

Burada önemli bir noktaya dikkat çekmek istiyorum. seç şablonuna parametre olarak verilen dizginin içinde kullanılan değişken isminin, seç işlevi içinde tanımlananla aynı olması şarttır. 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, c diye tek harflik isimler kullanılır.

Bunun bir örneği olarak std.algorithm modülündeki sort işlevine bakabiliriz. "Sırala" anlamına gelen sort, kendisine verilen aralığı sıralamaya yarar. sort işlevi, sıralama koşulunda kullanılan değişkenlerin a ve b olmalarını şart koşar. Konumuzla ilgisi olmayan bölümlerini göstermeden, sort'un tanımı şöyledir.

void sort(alias less = "a < b", /* ... */)(/* ... */)

less isimli şablon parametresinin varsayılan değeri, a ve b değişkenlerini kullanır. Oradaki a ve b, sort'un her sıralama kararında kullandığı soldaki ve sağdaki değişkenlerdir. O koşul soldakinin sağdakinden küçük olmasına baktığı için; less'in varsayılan sıralaması küçükten büyüğe doğrudur.

sort şablonu, öyle bir varsayılan değere sahip olduğu için, o parametre bildirilmeden kullanılabilir:

import std.stdio;
import std.algorithm;

void main()
{
    int[] sayılar = [ 5, 1, 4, 2, 3 ];
    sort(sayılar);
    writeln(sayılar);
}

Not: Dizilerin .sort niteliğinin de aynı amaçla kullanılabileceğini hatırlayın.

Beklendiği gibi, dizi elemanları sıralanmışlardır:

1 2 3 4 5

Elemanların ters sırada sıralanmaları için less parametresini açıkça yazmak ve < yerine > belirtmek yeter:

    int[] sayılar = [ 5, 1, 4, 2, 3 ];
    sort!"a > b"(sayılar);
    writeln(sayılar);

Bu sefer, her karşılaştırmada soldaki elemanın sağdaki elemandan büyük olması kıstası kullanıldığından dizi büyükten küçüğe doğru sıralanır:

5 4 3 2 1

Aslında aynı amaca ulaşmak için başka yollar da bulunduğunu hatırlayın. Örneğin İşlev Göstergeleri ve Kapamalar dersinde gördüğümüz gibi bir delegate parametre de kullanılabilir. Her zaman olduğu gibi, hangi durumda hangi yöntemin daha uygun olacağına kendiniz karar vermelisiniz.