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

aralık: [range], belirli biçimde erişilen bir grup eleman
çok şekillilik: [polymorphism], başka bir tür gibi davranabilmek
otomatik: [implicit], derleyici tarafından otomatik olarak yapılan
Phobos: [Phobos], D dilinin standart kütüphanesi
sol değer: [lvalue], adresi alınabilen değer
taşıma: [move], bir yerden bir yere kopyalamadan aktarma
... bütün sözlük



İngilizce Kaynaklar


Diğer




Başka Aralık Olanakları

Bundan önceki bölümdeki çoğu aralık örneğinde int aralıkları kullandık. Aslında topluluklar, algoritmalar, ve aralıklar, hep şablonlar olarak gerçekleştirilirler. Biz de bir önceki bölümde yazdır() işlevini şablon olarak tanımladığımız için farklı InputRange aralıklarıyla kullanabilmiştik:

void yazdır(T)(T aralık) {
    // ...
}

yazdır()'ın bir eksiği, şablon parametresinin bir InputRange olması gerektiği halde bunu bir şablon kısıtlaması ile belirtmiyor olmasıdır. (Şablon kısıtlamalarını Ayrıntılı Şablonlar bölümünde görmüştük.)

std.range modülü, hem şablon kısıtlamalarında hem de static if deyimlerinde yararlanılmak üzere çok sayıda yardımcı şablon içerir.

Aralık çeşidi şablonları

Bu şablonların "öyle midir" anlamına gelen is ile başlayanları, belirli bir türün o aralık çeşidinden olup olmadığını belirtir. Örneğin isInputRange!T, "T bir InputRange midir" sorusunu yanıtlar. Aralık çeşidini sorgulayan şablonlar şunlardır:

yazdır() işlevinin şablon kısıtlaması isInputRange'den yararlanarak şöyle yazılır:

void yazdır(T)(T aralık)
        if (isInputRange!T) {
    // ...
}

Bunlar arasından isOutputRange, diğerlerinden farklı olarak iki şablon parametresi alır: Birincisi desteklenmesi gereken aralık türünü, ikincisi ise desteklenmesi gereken eleman türünü belirler. Örneğin aralığın double türü ile uyumlu olan bir çıkış aralığı olması gerektiği şöyle belirtilir:

void birİşlev(T)(T aralık)
        if (isOutputRange!(T, double)) {
    // ...
}

O kısıtlamayı T bir OutputRange ise ve double türü ile kullanılabiliyorsa diye okuyabiliriz.

static if ile birlikte kullanıldıklarında bu şablonlar kendi yazdığımız aralıkların ne derece yetenekli olabileceklerini de belirlerler. Örneğin asıl aralık ForwardRange olduğunda save() işlevine de sahip olacağından, kendi yazdığımız özel aralık türünün de save() işlevini sunmasını o işlevden yararlanarak sağlayabiliriz.

Bunu görmek için kendisine verilen aralıktaki değerlerin ters işaretlilerini üreten bir aralığı önce bir InputRange olarak tasarlayalım:

import std.range;

struct Tersi(T)
        if (isInputRange!T) {
    T aralık;

    bool empty() {
        return aralık.empty;
    }

    auto front() {
        return -aralık.front;
    }

    void popFront() {
        aralık.popFront();
    }
}

Not: front'un dönüş türü olarak auto yerine biraz aşağıda göreceğimiz ElementType!T de yazılabilir.

Bu aralığın tek özelliği, front işlevinde asıl aralığın başındaki değerin ters işaretlisini döndürmesidir.

Çoğu aralık türünde olduğu gibi, kullanım kolaylığı açısından bir de yardımcı işlev yazalım:

Tersi!T tersi(T)(T aralık) {
    return Tersi!T(aralık);
}

Bu aralık örneğin bir önceki bölümde gördüğümüz FibonacciSerisi aralığıyla birlikte kullanılmaya hazırdır:

struct FibonacciSerisi {
    int baştaki = 0;
    int sonraki = 1;

    enum empty = false;

    int front() const {
        return baştaki;
    }

    void popFront() {
        const ikiSonraki = baştaki + sonraki;
        baştaki = sonraki;
        sonraki = ikiSonraki;
    }

    FibonacciSerisi save() const {
        return this;
    }
}

// ...

    writeln(FibonacciSerisi().take(5).tersi);

Çıktısı, aralığın ilk 5 elemanının ters işaretlilerini içerir:

[0, -1, -1, -2, -3]

Doğal olarak, Tersi bu tanımıyla yalnızca bir InputRange olarak kullanılabilir ve örneğin ForwardRange gerektiren cycle() gibi algoritmalara gönderilemez:

    writeln(FibonacciSerisi()
            .take(5)
            .tersi
            .cycle         // ← derleme HATASI
            .take(10));

Oysa, asıl aralık FibonacciSerisi gibi bir ForwardRange olduğunda Tersi'nin de save() işlevini sunamaması için bir neden yoktur. Bu durum derleme zamanında static if ile denetlenir ve ek işlevler asıl aralığın yetenekleri doğrultusunda tanımlanırlar. Bu durumda, asıl aralığın bir kopyası ile kurulmuş olan yeni bir Tersi nesnesi döndürmek yeterlidir:

struct Tersi(T)
        if (isInputRange!T) {
// ...

    static if (isForwardRange!T) {
        Tersi save() {
            return Tersi(aralık.save());
        }
    }
}

Yukarıdaki ek işlev sayesinde Tersi!FibonacciSerisi de artık bir ForwardRange olarak kabul edilir ve yukarıdaki cycle() satırı artık derlenir:

    writeln(FibonacciSerisi()
            .take(5)
            .tersi
            .cycle         // ← artık derlenir
            .take(10));

Çıktısı, Fibonacci serisinin ilk 5 elemanının ters işaretlilerinin sürekli olarak tekrarlanmasından oluşan aralığın ilk 10 elemanıdır:

[0, -1, -1, -2, -3, 0, -1, -1, -2, -3]

Tersi aralığının duruma göre BidirectionalRange ve RandomAccessRange olabilmesi de aynı yöntemle sağlanır:

struct Tersi(T)
        if (isInputRange!T) {
// ...

    static if (isBidirectionalRange!T) {
        auto back() {
            return -aralık.back;
        }

        void popBack() {
            aralık.popBack();
        }
    }

    static if (isRandomAccessRange!T) {
        auto opIndex(size_t sıraNumarası) {
            return -aralık[sıraNumarası];
        }
    }
}

Böylece örneğin dizilerle kullanıldığında elemanlara [] işleci ile erişilebilir:

    auto d = [ 1.5, 2.75 ];
    auto e = tersi(d);
    writeln(e[1]);

Çıktısı:

-2.75
ElementType ve ElementEncodingType

ElementType, aralıktaki elemanların türünü bildiren bir şablondur. ElementType!T, "T aralığının eleman türü" anlamına gelir.

Örneğin, belirli türden iki aralık alan ve bunlardan birincisinin elemanlarının türü ile uyumlu olan bir çıkış aralığı gerektiren bir işlevin şablon kısıtlaması şöyle belirtilebilir:

void işle(G1, G2, Ç)(G1 giriş1, G2 giriş2, Ç çıkış)
        if (isInputRange!G1 &&
            isForwardRange!G2 &&
            isOutputRange!(Ç, ElementType!G1)) {
    // ...
}

Yukarıdaki şablon kısıtlamasını şöyle açıklayabiliriz: G1 bir InputRange ve G2 bir ForwardRange olmalıdır; ek olarak, Ç de G1'in elemanlarının türü ile kullanılabilen bir OutputRange olmalıdır.

Dizgiler aralık olarak kullanıldıklarında elemanlarına harf harf erişildiği için dizgi aralıklarının eleman türü her zaman için dchar'dır. Bu yüzden dizgilerin UTF kodlama türü ElementType ile belirlenemez. UTF kodlama türünü belirlemek için ElementEncodingType kullanılır. Örneğin bir wchar dizgisinin ElementTypedchar, ElementEncodingType'ı da wchar'dır.

Başka aralık nitelikleri

std.range modülü aralıklarla ilgili başka şablon olanakları da sunar. Bunlar da şablon kısıtlamalarında ve static if deyimlerinde kullanılırlar.

Örneğin empty, isInfinite!T'nin değerine bağlı olarak farklı biçimde tanımlanabilir. Böylece, asıl aralık sonsuz olduğunda Tersi!T'nin de derleme zamanında sonsuz olması sağlanmış olur:

struct Tersi(T)
        if (isInputRange!T) {
// ...

    static if (isInfinite!T) {
        // Tersi!T de sonsuz olur
        enum empty = false;

    } else {
        bool empty() {
            return aralık.empty;
        }
    }

// ...
}

static assert( isInfinite!(Tersi!FibonacciSerisi));
static assert(!isInfinite!(int[]));
Çalışma zamanı çok şekilliliği için inputRangeObject() ve outputRangeObject()

Aralıklar, şablonların getirdiği derleme zamanı çok şekilliliğine sahiptirler. Biz de bir önceki ve bu bölümdeki çoğu örnekte bu olanaktan yararlandık. (Not: Derleme zamanı çok şekilliliği ile çalışma zamanı çok şekilliliğinin farklarını Ayrıntılı Şablonlar bölümündeki "Derleme zamanı çok şekilliliği" başlığında görmüştük.)

Derleme zamanı çok şekilliliğinin bir etkisi, şablonun her farklı kullanımının farklı bir şablon türü oluşturmasıdır. Örneğin take() algoritmasının döndürdüğü özel aralık nesnesinin türü take()'e gönderilen aralık türüne göre değişir:

    writeln(typeof([11, 22].tersi.take(1)).stringof);
    writeln(typeof(FibonacciSerisi().take(1)).stringof);

Çıktısı:

Take!(Tersi!(int[]))
Take!(FibonacciSerisi)

Bunun doğal sonucu, farklı türlere sahip olan aralık nesnelerinin uyumsuz oldukları için birbirlerine atanamamalarıdır. Bu uyumsuzluk iki InputRange nesnesi arasında daha açık olarak da gösterilebilir:

    auto aralık = [11, 22].tersi;
    // ... sonraki bir zamanda ...
    aralık = FibonacciSerisi();    // ← derleme HATASI

Bekleneceği gibi, derleme hatası FibonacciSerisi türünün Tersi!(int[]) türüne otomatik olarak dönüştürülemeyeceğini bildirir:

Error: cannot implicitly convert expression (FibonacciSerisi(0,1))
of type FibonacciSerisi to Tersi!(int[])

Buna rağmen, her ne kadar türleri uyumsuz olsalar da aslında her ikisi de int aralığı olan bu aralık nesnelerinin birbirlerinin yerine kullanılabilmelerini bekleyebiliriz. Çünkü kullanım açısından bakıldığında, bütün işleri int türünden elemanlara eriştirmek olduğundan, o elemanların hangi düzenek yoluyla üretildikleri veya eriştirildikleri önemli olmamalıdır.

Phobos, bu sorunu inputRangeObject() ve outputRangeObject() işlevleriyle giderir. inputRangeObject(), aralıkları belirli türden elemanlara sahip belirli çeşit aralık tanımıyla kullandırmaya yarar. Aralık nesnelerini türlerinden bağımsız olarak, örneğin elemanları int olan InputRange aralığı genel tanımı ile kullanabiliriz.

inputRangeObject() bütün erişim aralıklarını destekleyecek kadar esnektir. Bu yüzden nesnelerin tanımı auto ile yapılamaz; aralık nesnesinin nasıl kullanılacağının açıkça belirtilmesi gerekir:

    // "int giriş aralığı" anlamında
    InputRange!int aralık = [11, 22].tersi.inputRangeObject;

    // ... sonraki bir zamanda ...

    // Bu atama artık derlenir
    aralık = FibonacciSerisi().inputRangeObject;

inputRangeObject()'in döndürdüğü nesnelerin ikisi de InputRange!int olarak kullanılabilmektedir.

Aralığın örneğin int elemanlı bir ForwardRange olarak kullanılacağı durumda ise açıkça ForwardRange!int yazmak gerekir:

    ForwardRange!int aralık = [11, 22].tersi.inputRangeObject;

    auto kopyası = aralık.save;

    aralık = FibonacciSerisi().inputRangeObject;
    writeln(aralık.save.take(10));

Nesnelerin ForwardRange olarak kullanılabildiklerini göstermek için save() işlevlerini çağırarak kullandım.

outputRangeObject() de OutputRange aralıkları ile kullanılır ve onları belirli tür elemanlarla kullanılabilen OutputRange aralığı genel tanımına uydurur.

Özet