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

belirsiz sayıda parametre: [variadic], aynı işlevi (veya şablonu) farklı sayıda parametre ile çağırabilme olanağı
çokuzlu: [tuple], bir kaç parçanın diziye benzer biçimde bir araya gelmesinden oluşan yapı
döngü açılımı: [loop unrolling], döngü içeriğinin her eleman için art arda tekrarlanması
iç olanak: [core feature], dilin kütüphane gerektirmeyen bir olanağı
Phobos: [Phobos], D dilinin standart kütüphanesi
... bütün sözlük



İngilizce Kaynaklar


Diğer




Çokuzlular

Çokuzlu birden fazla değeri bir araya getirerek hep birden bir yapı nesnesi gibi kullanılmalarını sağlayan olanaktır. Bazı dillerin iç olanağı olan çokuzlular D'de std.typecons modülündeki Tuple ile bir kütüphane olanağı olarak gerçekleştirilmiştir.

Tuple bazı işlemleri için std.meta modülündeki AliasSeq'ten da yararlanır.

Tuple ve tuple()

Çokuzlular Tuple şablonu ile gerçekleştirilmişlerdir. Çoğunlukla kolaylık işlevi olan tuple() ile oluşturulurlar:

import std.stdio;
import std.typecons;

void main() {
    auto çokuzlu = tuple(42, "merhaba");
    writeln(çokuzlu);
}

Yukarıdaki tuple() işlevi 42 değerindeki int'in ve "merhaba" değerindeki string'in bir araya gelmesinden oluşan bir nesne oluşturur. Bu nesnenin türünü ve üyelerinin değerlerini programın çıktısında görüyoruz:

Tuple!(int, string)(42, "merhaba")

O çokuzlu türünün aşağıdaki sözde yapının eşdeğeri olduğunu ve perde arkasında da öyle gerçekleştirildiğini düşünebilirsiniz:

// Tuple!(int, string)'in eşdeğeri
struct __Çokuzlu_int_string {
    int __üye_0;
    string __üye_1;
}

Çokuzluların üyelerine normalde sıra numarasıyla erişilir. Bu açıdan bakıldığında her bir üyesi farklı türden olabilen bir dizi gibi düşünülebilir:

    writeln(çokuzlu[0]);
    writeln(çokuzlu[1]);

Çıktısı:

42
merhaba
Üye isimleri

Çokuzlu üyelerine sıra numarasıyla erişilmesi başka dillerde de yaygındır. Phobos çokuzlularında ise üyelere nitelik isimleriyle de erişilebilir. Bunun için çokuzlunun tuple() kolaylık işlevi ile değil, Tuple şablonu ile açıkça oluşturulması gerekir. Üyelerin türleri ve nitelik isimleri çiftler halinde belirtilirler:

    auto çokuzlu = Tuple!(int, "sayı",
                          string, "mesaj")(42, "merhaba");

Yukarıdaki tanım, int türündeki 0 numaralı üyeye ayrıca .sayı niteliğiyle ve string türündeki 1 numaralı üyeye ayrıca .mesaj niteliğiyle erişilmesini sağlar:

    writeln("0 sıra numarasıyla    : ", çokuzlu[0]);
    writeln(".sayı niteliği olarak : ", çokuzlu.sayı);
    writeln("1 sıra numarasıyla    : ", çokuzlu[1]);
    writeln(".mesaj niteliği olarak: ", çokuzlu.mesaj);

Çıktısı:

0 sıra numarasıyla    : 42
.sayı niteliği olarak : 42
1 sıra numarasıyla    : merhaba
.mesaj niteliği olarak: merhaba
Üyelerin değer listesi olarak açılmaları

Çokuzlu nesneleri üyelerinin değerlerinden oluşan liste olarak açılabilir ve örneğin o türlere uyan bir işlevi çağırırken kullanılabilir. Bu, .expand niteliği ile veya çokuzlu nesnesi dilimlenerek sağlanır:

import std.stdio;
import std.typecons;

void foo(int i, string s, double d, char c) {
    // ...
}

void bar(int i, double d, char c) {
    // ...
}

void main() {
    auto ç = tuple(1, "2", 3.3, '4');

    // İkisi de foo(1, "2", 3.3, '4')'ün eşdeğeridir:
    foo(ç.expand);
    foo(ç[]);

    // bar(1, 3.3, '4')'ün eşdeğeridir:
    bar(ç[0], ç[$-2..$]);
}

Yukarıdaki çokuzlu int, string, double, ve char türündeki değerlerden oluşmaktadır. Bu yüzden, bütün üyelerinin açılmasından oluşan liste foo()'nun parametre listesine uyar ve o yüzden foo() çağrılırken kullanılabilir. bar() çağrılırken ise yalnızca ilk üyesinin ve son iki üyesinin değerlerinden oluşan üç değer gönderilmektedir.

Üyelerin türleri aynı dizinin elemanı olabilecek kadar uyumlu olduklarında çokuzlunun açılımı bir diziyi ilklerken de kullanılabilir:

import std.stdio;
import std.typecons;

void main() {
    auto çokuzlu = tuple(1, 2, 3);
    auto dizi = [ çokuzlu.expand, çokuzlu[] ];
    writeln(dizi);
}

Yukarıdaki örnek dizi üç int'ten oluşan çokuzlunun iki kere açılmasından oluşmaktadır:

[1, 2, 3, 1, 2, 3]
Derleme zamanı foreach'i

Hem dizi gibi düşünülebildiklerinden hem de değerleri liste olarak açılabildiğinden çokuzlular foreach ile de kullanılabilirler:

    auto çokuzlu = tuple(42, "merhaba", 1.5);

    foreach (i, üye; çokuzlu) {
        writefln("%s: %s", i, üye);
    }

Çıktısı:

0: 42
1: merhaba
2: 1.5

Yukarıdaki koddaki foreach'in çalışma zamanında işletildiği düşünülebilir; ancak, bu doğru değildir. Çokuzlu üyeleri üzerinde işletilen foreach'ler aslında döngü değil, döngünün içeriğinin üye adedi kadar tekrarlanmasından oluşan bir döngü açılımıdır. Dolayısıyla, yukarıdaki foreach döngüsü aşağıdaki üç kod bloğunun eşdeğeridir:

    {
        enum size_t i = 0;
        int üye = çokuzlu[i];
        writefln("%s: %s", i, üye);
    }
    {
        enum size_t i = 1;
        string üye = çokuzlu[i];
        writefln("%s: %s", i, üye);
    }
    {
        enum size_t i = 2;
        double üye = çokuzlu[i];
        writefln("%s: %s", i, üye);
    }

Bunun nedeni, her çokuzlu üyesinin farklı türden olabilmesi ve dolayısıyla döngünün her ilerletilişinde döngü kapsamındaki kodların farklı olarak derlenmesinin gerekmesidir.

Birden fazla değer döndürmek

Çokuzlular işlevlerin tek değer döndürebilme yetersizliklerine karşı basit bir çözüm olarak görülebilirler. Örneğin, std.algorithm modülündeki findSplit() bir aralığı başka bir aralık içinde arar ve arama sonucunda üç bilgi üretir: bulunan aralıktan öncesi, bulunan aralık, ve bulunan aralıktan sonrası. findSplit(), bu üç parça bilgiyi bir çokuzlu olarak döndürür:

import std.algorithm;

// ...

    auto bütünAralık = "merhaba";
    auto aranan = "er";

    auto sonuç = findSplit(bütünAralık, aranan);

    writeln("öncesi : ", sonuç[0]);
    writeln("bulunan: ", sonuç[1]);
    writeln("sonrası: ", sonuç[2]);

Çıktısı:

öncesi : m
bulunan: er
sonrası: haba

Birden fazla değer döndürmek için bir yapı nesnesi de döndürülebileceğini biliyorsunuz:

struct Sonuç {
    // ...
}

Sonuç işlev() {
    // ...
}
AliasSeq

std.meta modülünde tanımlı olan AliasSeq normalde derleyiciye ait olan ve hep üstü kapalı olarak geçen ve yukarıda da rastladığımız bir kavramı programcının kullanımına sunar: virgüllerle ayrılmış değer listesi. Aşağıda bunun üç örneğini görmekteyiz:

Bu üç farklı listenin örnekleri şöyle gösterilebilir:

    işlev(1, "merhaba", 2.5);             // işlev parametreleri
    auto n = YapıŞablonu!(char, long)();  // şablon parametreleri
    auto d = [ 1, 2, 3, 4 ];              // dizi elemanları

Daha yukarıda örneklerini gördüğümüz Tuple üyelerinin değer listesi olarak açılabilmeleri de aslında AliasSeq tarafından sağlanır.

AliasSeq'in adı "sıralanmış isimler" olarak açıklanabilen "alias sequence"tan gelir ve türler, değerler, ve isimler içerir. (AliasSeq ve std.meta'nın eski isimleri sırasıyla TypeTuple ve std.typetuple idi.)

Bu bölümde AliasSeq'in yalnızca ya bütünüyle türlerden ya da bütünüyle değerlerden oluşan örneklerini göreceğiz. Hem türlerden hem değerlerden oluşan örneklerini bir sonraki bölüme ayıracağız. AliasSeq bir sonraki bölümde göreceğimiz belirsiz sayıda parametre alan şablonlarda da yararlıdır.

Değerlerden oluşan AliasSeq

Bir derleme zamanı olanağı olan AliasSeq, ifade ettiği parametre listesini kendi şablon parametreleri olarak alır. Bunu üç parametre alan bir işlev çağrısında görelim:

import std.stdio;

void foo(int i, string s, double d) {
    writefln("foo çağrıldı: %s %s %s", i, s, d);
}

Normalde o işlevin açıkça üç parametre değeri ile çağrıldığını biliyoruz:

    foo(1, "merhaba", 2.5);

AliasSeq o parametre değerlerini tek değişken olarak bir arada tutabilir ve işlev çağrıları sırasında otomatik olarak parametre listesi olarak açılabilir:

import std.meta;

// ...

    alias parametreler = AliasSeq!(1, "merhaba", 2.5);
    foo(parametreler);

Her ne kadar bu sefer tek değer alıyormuş gibi görünse de, yukarıdaki foo çağrısı öncekinin eşdeğeridir ve her iki yöntem de aynı çıktıyı üretir:

foo çağrıldı: 1 merhaba 2.5

parametreler'in auto anahtar sözcüğü ile değişken olarak değil, alias sözcüğü ile belirli bir AliasSeq'in takma ismi olarak tanımlandığına dikkat edin. auto anahtar sözcüğünün kullanılabildiği durumlar olsa da bu bölümdeki örneklerde yalnızca takma isim olarak göreceğiz.

Yukarıda Tuple başlığı altında da gördüğümüz gibi, değerlerin hepsi aynı türden veya daha genel olarak uygun türlerden olduklarında, AliasSeq bir dizi hazır değerinin elemanlarını da temsil edebilir:

    alias elemanlar = AliasSeq!(1, 2, 3, 4);
    auto dizi = [ elemanlar ];
    assert(dizi == [ 1, 2, 3, 4 ]);
Türlerden oluşan AliasSeq

AliasSeq'in parametreleri türlerin kendilerinden de oluşabilir. Yani, belirli bir türün belirli bir değeri değil, int gibi bir türün kendisi olabilir.

Tür içeren AliasSeq'ler şablonlarla kullanılmaya elverişlidir. Bunun bir örneğini görmek için iki parametreli bir yapı şablonu düşünelim. Bu şablonun ilk parametresi yapının bir dizisinin eleman türünü, ikincisi de yapının bir işlevinin dönüş türünü belirliyor olsun:

import std.conv;

struct S(ElemanTürü, SonuçTürü) {
    ElemanTürü[] dizi;

    SonuçTürü uzunluk() {
        return to!SonuçTürü(dizi.length);
    }
}

void main() {
    auto s = S!(double, int)([ 1, 2, 3 ]);
    auto u = s.uzunluk();
}

Yukarıda bu şablonun (double, int) türleri ile kullanıldığını görüyoruz. Aynı amaç için iki tür içeren bir AliasSeq'ten de yararlanılabilir:

import std.meta;

// ...

    alias Türler = AliasSeq!(double, int);
    auto s = S!Türler([ 1, 2, 3 ]);

Yukarıda S şablonu her ne kadar tek şablon parametresi ile kullanılıyor gibi görünse de, Türler otomatik olarak açılır ve sonuçta S!(double, int) türünün aynısı elde edilir.

AliasSeq özellikle belirsiz sayıda parametre alan şablonlarda yararlıdır. Bunun örneklerini bir sonraki bölümde göreceğiz.

Dizi gibi kullanılması

AliasSeq'in kurulduğu şablon parametrelerine dizi erişim işleci ile erişilebilir:

    alias parametreler = AliasSeq!(1, "merhaba", 2.5);
    assert(parametreler[0] == 1);
    assert(parametreler[1] == "merhaba");
    assert(parametreler[2] == 2.5);

AliasSeq yine dizilerde olduğu gibi dilimleme işleminde de kullanılabilir. Yukarıdaki örneklerde kullanılan AliasSeq'in son iki parametresine uyan bir işlev olduğunu düşünelim. Böyle bir işlev yukarıdaki parametreler'in son iki değeri dilimlenerek çağrılabilir:

void bar(string s, double d) {
    // ...
}

// ...

    bar(parametreler[$-2 .. $]);
foreach ile kullanılması

Yukarıda gördüğümüz Tuple'da olduğu gibi, AliasSeq'in foreach ile kullanılmasında da çalışma zamanında işletilen bir döngü oluşmaz; döngünün içeriği parametre listesindeki her eleman için derleme zamanında kod olarak açılır ve sonuçta o açılım derlenir.

Bunun örneğini yukarıdaki S yapı şablonu için yazılmış olan bir birim testinde görelim. Aşağıdaki kod bu yapı şablonunun eleman türü olarak int, long, ve float kullanılabildiğini test ediyor (SonuçTürü ise hep size_t):

unittest {
    alias Türler = AliasSeq!(int, long, float);

    foreach (Tür; Türler) {
        auto s = S!(Tür, size_t)([ Tür.init, Tür.init ]);
        assert(s.uzunluk() == 2);
    }
}

Yukarıdaki koddaki Tür değişkeni sırasıyla int, long, ve float türünü temsil eder ve sonuçta foreach döngüsü aşağıdaki eşdeğer döngü açılımı olarak derlenir:

    {
        auto s = S!(int, size_t)([ int.init, int.init ]);
        assert(s.uzunluk() == 2);
    }
    {
        auto s = S!(long, size_t)([ long.init, long.init ]);
        assert(s.uzunluk() == 2);
    }
    {
        auto s = S!(float, size_t)([ float.init, float.init ]);
        assert(s.uzunluk() == 2);
    }
.tupleof niteliği

.tupleof bir türün veya bir nesnenin bütün üyelerini bir çokuzlu olarak elde etmeye yarar. Aşağıdaki örnek .tupleof niteliğini bir türe uyguluyor:

import std.stdio;

struct Yapı {
    int numara;
    string dizgi;
    double kesirli;
}

void main() {
    foreach (i, ÜyeTürü; typeof(Yapı.tupleof)) {
        writefln("Üye %s:", i);
        writefln("  tür : %s", ÜyeTürü.stringof);

        string isim = Yapı.tupleof[i].stringof;
        writefln("  isim: %s", isim);
    }
}

Yapı.tupleof'un yukarıda iki yerde geçtiğine dikkat edin. İlkinde eleman türleri typeof ile elde edilmekte ve her tür foreach'in ÜyeTürü değişkeni olarak belirmektedir. İkincisinde ise yapı üyesinin ismi Yapı.tupleof[i].stringof ile elde edilmektedir.

Üye 0:
  tür : int
  isim: numara
Üye 1:
  tür : string
  isim: dizgi
Üye 2:
  tür : double
  isim: kesirli

.tupleof bir nesneye uygulandığında ise o nesnenin üyelerinin değerlerini çokuzlu olarak elde etmeye yarar:

    auto nesne = Yapı(42, "merhaba", 1.5);

    foreach (i, üye; nesne.tupleof) {
        writefln("Üye %s:", i);
        writefln("  tür  : %s", typeof(üye).stringof);
        writefln("  değer: %s", üye);
    }

Bu durumda üye isimli döngü değişkeni sırasıyla üyelerin değerlerini temsil eder:

Üye 0:
  tür  : int
  değer: 42
Üye 1:
  tür  : string
  değer: merhaba
Üye 2:
  tür  : double
  değer: 1.5

Buradaki önemli bir ayrıntı, nesneye uygulanan .tupleof çokuzlusunun üyelerin değerlerinden değil, üyelerin kendilerinden oluşmasıdır. Bir anlamda, her çokuzlu üyesi temsil ettiği asıl üyenin referansıdır.

Özet