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

değişken: [variable], kavramları temsil eden veya sınıf nesnesine erişim sağlayan program yapısı
değişmez: [immutable], programın çalışması süresince kesinlikle değişmeyen
fonksiyonel programlama: [functional programming], yan etki üretmeme ilkesine dayalı programlama yöntemi
işlev: [function], programdaki bir kaç adımı bir araya getiren program parçası
kapsam: [scope], küme parantezleriyle belirlenen bir alan
parametre: [parameter], işleve işini yapması için verilen bilgi
sabit: [const], bir bağlamda değiştirilmeyen
takma isim: [alias], var olan bir olanağın başka bir ismi
tür nitelendirici: [type qualifier], const, immutable, shared, ve inout
... bütün sözlük



İngilizce Kaynaklar


Diğer




Değişmezlik

Kavramlar, programlarda kullanılan değişkenlerle temsil edilir. Kavramlar arasındaki etkileşimleri bu değişkenlerin değerlerini değiştirerek sağlarız. Örneğin, aşağıdaki kod bir alışveriş ile ilgili değişkenlerin değerlerini değiştirmektedir:

    toplamFiyat = fiyatıHesapla(fiyatListesi);
    cüzdandakiMiktar -= toplamFiyat;
    bakkaldakiMiktar += toplamFiyat;

Değer değişiminin bilerek kısıtlanmasına değişmezlik denir.

Değer değişimi çoğu iş için gerekli olduğundan değişimin bilerek kısıtlanması anlamsız gibi görünebilse de oldukça güçlü ve yararlı bir olanaktır. Değişmezlik kavramı, yazılım dünyası tarafından edinilmiş olan deneyimlere dayanır: Değişmezlik, kodların doğruluğuna ve kolay değiştirilebilmelerine katkı sağlar. Hatta, bazı fonksiyonel programlama dilleri değer değişimini bütünüyle yasaklarlar.

Değişmezliğin getirdiği bazı yararlar şunlardır:

Değişmezlik, hem genel olarak programlamada çok yaygın olduğundan hem de D programcılığında çok kullanıldığından, bu kavramla ilgili aşağıdaki garipliklere göz yumulur:

Değişmezler

Kesinlikle değişmeyecek olan değişkenler üç farklı biçimde tanımlanabilirler.

enum değişkenler

Bazı sabit değişkenlerin enum olarak tanımlanabildiklerini enum bölümünde görmüştük:

    enum dosyaİsmi = "liste.txt";

Derleme zamanında hesaplanabildikleri sürece enum değişkenler işlev çağrıları da dahil olmak üzere daha karmaşık ifadelerle de ilklenebilirler:

int satırAdedi() {
    return 42;
}

int sütunAdedi() {
    return 7;
}

string isim() {
    return "liste";
}

void main() {
    enum dosyaİsmi = isim() ~ ".txt";
    enum toplamKare = satırAdedi() * sütunAdedi();
}

Bunu sağlayan D olanağı, ilerideki bir bölümde göreceğimiz derleme zamanında işlev işletme olanağıdır (CTFE).

Derleyici enum değişkenlerin değiştirilmelerine izin vermez:

    ++toplamKare;    // ← derleme HATASI

Değişmezlik kavramını sağlayan çok etkili bir olanak olmasına karşın enum ancak değerleri derleme zamanında bilinen veya hesaplanabilen sabitler için kullanılabilir.

Bekleneceği gibi, program derlenirken enum değişkenlerin yerlerine onların değerleri kullanılır. Örneğin, şöyle bir enum tanımı ve onu kullanan iki ifade olsun:

    enum i = 42;
    writeln(i);
    foo(i);

Yukarıdaki kod, i'nin yerine onun değeri olan 42'nin doğrudan yazılmasının eşdeğeridir:

    writeln(42);
    foo(42);

Bir enum değişkenin yerine değerinin kullanılıyor olması int gibi basit türler için normal olarak kabul edilmelidir. Ancak, enum değişkenlerin dizi veya eşleme tablosu olarak kullanılmalarının gizli bir bedeli vardır:

    enum a = [ 42, 100 ];
    writeln(a);
    foo(a);

a'nın yerine değerini yerleştirdiğimizde derleyicinin derleyeceği asıl kodun aşağıdaki gibi olduğunu görürüz:

    writeln([ 42, 100 ]);    // bir dizi oluşturulur
    foo([ 42, 100 ]);        // başka bir dizi oluşturulur

Yukarıdaki koddaki gizli bedel, her ifade için farklı bir dizi oluşturuluyor olmasıdır. Bu yüzden, birden fazla yerde kullanılacak olan dizilerin ve eşleme tablolarının immutable değişkenler olarak tanımlanmaları çoğu duruma daha uygundur.

const değişkenler

enum gibi, bu anahtar sözcük de bir değişkenin değerinin değişmeyeceğini bildirir. enum'dan farkı, const değişkenlerin adresleri olan normal değişkenler olmaları ve ilk değerlerini çalışma zamanında da alabilmeleridir.

Derleyici const değişkenlerin değiştirilmelerine izin vermez:

    const yarısı = toplam / 2;
    yarısı = 10;    // ← derleme HATASI

Aşağıdaki program enum ve const anahtar sözcüklerinin kullanımlarının farklarını gösteriyor. Tuttuğu sayıyı kullanıcının tahmin etmesini bekleyen bu programda tutulan sayı derleme zamanında bilinemeyeceğinden enum olarak tanımlanamaz. Ancak, bir kere seçildikten sonra değerinin değişmesi istenmeyeceğinden ve hatta değişmesi bir hata olarak kabul edileceğinden bu değişkenin const olarak işaretlenmesi uygun olur.

Aşağıdaki program kullanıcının tahminini okurken yine bir önceki bölümde tanımladığımız sayıOku işlevinden yararlanıyor:

import std.stdio;
import std.random;

int sayıOku(string mesaj) {
    int sayı;
    write(mesaj, "? ");
    readf(" %s", &sayı);
    return sayı;
}

void main() {
    enum enAz = 1;
    enum enÇok = 10;

    const sayı = uniform(enAz, enÇok + 1);

    writefln("%s ile %s arasında bir sayı tuttum.",
             enAz, enÇok);

    auto doğru_mu = false;
    while (!doğru_mu) {
        const tahmin = sayıOku("Tahmininiz");
        doğru_mu = (tahmin == sayı);
    }

    writeln("Doğru!");
}

Gözlemler:

Program içinde açıkça const(int) diye parantezle yazılması gerekmese de const türün bir parçasıdır. Aşağıdaki program üç farklı biçimde tanımlanmış olan değişkenlerin türlerinin tam isimlerinin aynı olduklarını gösteriyor:

import std.stdio;

void main() {
    const      çıkarsanarak = 0;
    const int  türüyle      = 1;
    const(int) tamOlarak    = 2;

    writeln(typeof(çıkarsanarak).stringof);
    writeln(typeof(türüyle).stringof);
    writeln(typeof(tamOlarak).stringof);
}

Üçünün de asıl tür ismi const'ı da içerir ve parantezlidir:

const(int)
const(int)
const(int)

Parantezlerin içindeki tür önemlidir. Bunu aşağıda dilimin veya elemanlarının değişmezliği konusunda göreceğiz.

immutable değişkenler

Değişken tanımında immutable anahtar sözcüğü const ile aynıdır. immutable değişkenler değiştirilemezler:

    immutable yarısı = toplam / 2;
    yarısı = 10;    // ← derleme HATASI

Programın başka tarafları özellikle immutable gerektirmediğinde, değişkenleri const veya immutable olarak tanımlayabilirsiniz. Bir işlevin özellikle immutable gerektirdiği durumda ise o parametreye gönderilecek olan değişkenin de immutable olarak tanımlanmış olması gerekir. Bunu aşağıda göreceğiz.

Parametreler

Sonraki iki bölümde göreceğimiz gibi, işlevler parametrelerinde değişiklik yapabilirler. Örneğin, parametre olarak gönderilmiş olan dilimlerin elemanlarını değiştirebilirler.

Başka Dizi Olanakları bölümünden hatırlayacağınız gibi, dilimler kendi elemanlarına sahip değillerdir, o elemanlara yalnızca erişim sağlarlar. Belirli bir anda aynı elemana erişim sağlamakta olan birden fazla dilim bulunabilir.

Bu başlık altındaki örneklerde dilimlerden yararlanıyor olsam da burada anlatılanlar eşleme tabloları için de geçerlidir çünkü onlar da referans türleridir.

İşlev parametresi olan bir dilim, işlevin çağrıldığı yerdeki dilimin kendisi değil, bir kopyasıdır. (Yalnızca dilim değişken kopyalanır, elemanları değil.)

import std.stdio;

void main() {
    int[] dilim = [ 10, 20, 30, 40 ];  // 1
    yarıla(dilim);
    writeln(dilim);
}

void yarıla(int[] sayılar) {           // 2
    foreach (ref sayı; sayılar) {
        sayı /= 2;
    }
}

Yukarıdaki yarıla işlevinin işletildiği sırada aynı dört elemana erişim sağlamakta olan iki farklı dilim vardır:

  1. main'in içinde tanımlanmış olan ve yarıla'ya parametre olarak gönderilen dilim isimli dilim
  2. yarıla'nın parametre değeri olarak almış olduğu ve main'deki dilimle aynı dört elemana erişim sağlamakta olan sayılar isimli dilim

foreach döngüsünde ref anahtar sözcüğü de kullanılmış olduğundan o dört elemanın değerleri yarılanmış olur:

[5, 10, 15, 20]

Bu örnekte de görüldüğü gibi, yarıla gibi işlevlerin kendilerine gönderilen dilimlerin elemanlarını değiştirebilmeleri kullanışlıdır çünkü zaten eleman değiştirmek için yazılmışlardır.

Derleyici, const değişkenlerin böyle işlevlere gönderilmelerine izin vermez:

    const int[] dilim = [ 10, 20, 30, 40 ];
    yarıla(dilim);    // ← derleme HATASI

Derleme hatası, const(int[]) türündeki bir değişkenin int[] türündeki bir parametre değeri olarak kullanılamayacağını bildirir:

Error: function deneme.yarıla (int[] sayılar) is not callable
using argument types (const(int[]))
const parametreler

const değişkenlerin yarıla'da olduğu gibi parametrelerinde değişiklik yapan işlevlere gönderilmelerinin engellenmesi önemlidir. Ancak, parametrelerinde değişiklik yapmayan ve hatta yapmaması gereken aşağıdaki yazdır gibi işlevlere gönderilememeleri büyük bir kısıtlama olarak görülmelidir:

import std.stdio;

void main() {
    const(int[]) dilim = [ 10, 20, 30, 40 ];
    yazdır(dilim);    // ← derleme HATASI
}

void yazdır(int[] dilim) {
    writefln("%s eleman: ", dilim.length);

    foreach (i, eleman; dilim) {
        writefln("%s: %s", i, eleman);
    }
}

Elemanların const olarak tanımlanmış olmaları, onların yazdırılmalarına engel olmamalıdır. const parametreler bu konuda yararlıdırlar. (const kavramını böylece doğru kullandığı düşünülen işlevlerin "const konusunda doğru" anlamında "const-correct" oldukları söylenir.)

const anahtar sözcüğü bir değişkenin belirli bir referans (örneğin dilim) yoluyla değiştirilmeyeceğini belirler. Parametreyi const olarak işaretlemek, o dilimin elemanlarının işlev içerisinde değiştirilemeyeceğini garanti eder. Böyle bir garanti sağlandığı için program artık derlenir:

    yazdır(dilim);    // şimdi derlenir
// ...
void yazdır(const int[] dilim) {
    // ...
}

İşlev nasıl olsa değiştirmeyeceğine söz vermiş olduğundan, hem değişebilen, hem const, hem de immutable değişkenler o işlevin const parametresi olarak gönderilebilirler:

    int[] değişebilenDilim = [ 7, 8 ];
    yazdır(değişebilenDilim);    // derlenir

    const int[] dilim = [ 10, 20, 30, 40 ];
    yazdır(dilim);               // derlenir

    immutable int[] immDilim = [ 1, 2 ];
    yazdır(immDilim);            // derlenir

İşlev tarafından değiştirilmediği halde const olarak tanımlanmayan bir parametre, işlevin kullanışlılığını düşürür. Böyle işlevlerin "const-correct" olmadıkları söylenir.

const parametrelerin başka bir yararı, programcıya verdikleri yararlı bilgidir: Değişkenin işlev tarafından değiştirilmeyeceğini bilmek kodun anlaşılırlığını arttırır.

const parametrelerin değişebilen, const, ve immutable değişkenleri kabul edebilmelerinin ilginç bir etkisi vardır. Bunu aşağıdaki "const parametre mi, immutable parametre mi?" başlığı altında göreceğiz.

in parametreler

Bir sonraki bölümde göreceğimiz gibi, in hem const anlamını içerir, hem de -preview=in derleyici seçeneği ile kullanıldığında daha yararlıdır. Bu yüzden, const parametreler yerine in parametreler kullanmanızı öneririm.

immutable parametreler

Hem değişebilen, hem const, hem de immutable değişkenler alabildiklerinden const parametrelerin esnek olduklarını söyleyebiliriz.

Öte yandan, bir parametrenin immutable olarak işaretlenmesi, asıl değişkenin de immutable olması şartını getirir. Bu açıdan bakıldığında immutable parametreler işlevin çağrıldığı nokta üzerinde kuvvetli bir talepte bulunmaktadırlar:

void birİşlem(immutable int[] dilim) {
    // ...
}

void main() {
    immutable int[] değişmezDilim = [ 1, 2 ];
    int[] değişebilenDilim = [ 8, 9 ];

    birİşlem(değişmezDilim);       // bu derlenir
    birİşlem(değişebilenDilim);    // ← derleme HATASI
}

O yüzden immutable parametreleri ancak gerçekten gereken durumlarda düşünmenizi öneririm. Şimdiye kadar öğrendiklerimiz arasında immutable parametreler yalnızca dizgi türlerinde üstü kapalı olarak geçerler. Bunu biraz aşağıda göstereceğim.

const veya immutable olarak işaretlenmiş olan parametrelerin, işlevin çağrıldığı yerdeki asıl değişkeni değiştirmeme sözü verdiklerini gördük. Bu konu yalnızca referans türünden olan değişkenlerle ilgilidir.

Referans ve değer türlerini bir sonraki bölümde daha ayrıntılı olarak göreceğiz. Bu bölüme kadar gördüğümüz türler arasında dilimler ve eşleme tabloları referans türleri, diğerleri ise değer türleridir.

const parametre mi, immutable parametre mi?

Not: in parametreler const anlamını içerdiklerinden, bu bölüm in parametrelerle de ilgilidir.

Yukarıdaki başlıklara bakıldığında esneklik getirdiği için const belirtecinin yeğlenmesinin doğru olacağı sonucuna varılabilir. Bu her zaman doğru değildir.

Parametre tanımındaki const belirteci, asıl değişkenin değişebilen mi, const mı, yoksa immutable mı olduğu bilgisini işlev içerisinde belirsiz hale getirir. Bunu derleyici de bilemez.

Bunun bir etkisi, const parametrelerin immutable parametre alan başka işlevlere doğrudan gönderilemeyecekleridir. Örneğin, değişken main içinde her ne kadar immutable olarak tanımlanmış bile olsa, aşağıdaki koddaki foo işlevi const parametresini bar'a gönderemez:

void main() {
    /* Asıl değişken immutable */
    immutable int[] dilim = [ 10, 20, 30, 40 ];
    foo(dilim);
}

/* Daha kullanışlı olabilmek için parametresini const olarak
 * alan bir işlev. */
void foo(const int[] dilim) {
    bar(dilim);    // ← derleme HATASI
}

/* Parametresini belki de geçerli bir nedenle immutable olarak
 * alan bir işlev. */
void bar(immutable int[] dilim) {
    // ...
}

bar, parametresinin immutable olmasını şart koşmaktadır. Öte yandan, foo'nun const parametresi olan dilim'in aslında immutable bir değişkene mi yoksa değişebilen bir değişkene mi bağlı olduğu bilinemez.

Not: Yukarıdaki kullanıma bakıldığında main içindeki asıl değişkenin immutable olduğu açıktır. Buna rağmen, derleyici her işlevi ayrı ayrı derlediğinden foo'nun const parametresinin işlevin her çağrıldığı noktada aslında immutable olduğunu bilmesi olanaksızdır. Derleyicinin gözünde dilim değişebilen de olabilir immutable da.

Böyle bir durumda bir çözüm, bar'ı parametrenin değişmez bir kopyası ile çağırmaktır:

void foo(const int[] dilim) {
    bar(dilim.idup);
}

Bu durumda kodun derlenmesi sağlanmış olsa da, asıl değişkenin zaten immutable olduğu durumda bile kopyasının alınıyor olmasının gereksiz bir bedeli olacaktır.

Bütün bunlara bakıldığında belki de foo'nun parametresini const olarak almasının her zaman için doğru olmadığı düşünülebilir. Çünkü parametresini baştan immutable olarak seçmiş olsa kod kopyaya gerek kalmadan derlenebilir:

void foo(immutable int[] dilim) {  // Bu sefer immutable
    bar(dilim);    // Artık kopya gerekmez
}

Ancak, bir üstteki başlıkta belirtildiği gibi, asıl değişkenin immutable olmadığı durumlarda foo'nun çağrılabilmesi için bu sefer de .idup ile kopyalanması gerekecekti:

    foo(değişebilenDilim.idup);

Görüldüğü gibi, değişmeyecek olan parametrenin türünün const veya immutable olarak belirlenmesinin kararı kolay değildir.

İleride göreceğimiz şablonlar bu konuda da yararlıdırlar. Aşağıdaki kodları kitabın bu aşamasında anlamanızı beklemesem de parametrenin değişebilen, const, veya immutable olması kararını ortadan kaldırdığını belirtmek istiyorum. Aşağıdaki foo, yalnızca asıl değişken immutable olmadığında kopya bedeli öder; immutable değişkenler kopyalanmazlar:

import std.conv;
// ...

/* Şablon olduğu için hem değişebilen hem de immutable
 * değişkenlerle çağrılabilir. */
void foo(T)(T[] dilim) {
    /* Asıl değişken zaten immutable olduğunda 'to' ile
     * kopyalamanın bedeli yoktur. */
    bar(to!(immutable T[])(dilim));
}
İlkleme

Değişimin engellenmesinin, değişken ilk değerlerinin az da olsa karmaşık ifadelerden oluştuğu durumlarda kısıtlayıcı olduğu düşünülebilir. Örneğin, aşağıdaki meyveler dizisinin elemanlarının, turunçgilEklensin_mi değişkeninin değerine göre belirlenmesi istenmiştir, ancak kod dizi const olduğundan derlenemez:

    const meyveler = [ "elma", "armut" ];

    if (turunçgilEklensin_mi) {
        meyveler ~= [ "portakal" ];    // ← derleme HATASI
    }

Değişkeni örneğin auto ile tanımlamak kodun derlenmesi için yeterli olsa da, daha iyi bir yöntem, const'ı kullanmaya devam etmek ama ilkleme kodunu bir işleve taşımaktır:

bool turunçgilEklensin_mi;

string[] meyvelerYap() {
    auto sonuç = [ "elma", "armut" ];

    if (turunçgilEklensin_mi) {
        sonuç ~= [ "portakal" ];
    }

    return sonuç;
}

void main() {
    const meyveler = meyvelerYap();
}

sonuç dizisinin değişebilen türden olduğu halde meyveler'in istendiği gibi const olabildiğine dikkat edin. Kodun bir işleve taşınmasının mümkün olmadığı veya güçlük doğurduğu durumlarda isimsiz işlevler kullanılabilir:

    const meyveler = {
      // 'meyvelerYap()' işlevinin içeriğinin aynısı
      auto sonuç = [ "elma", "armut" ];

      if (turunçgilEklensin_mi) {
          sonuç ~= [ "portakal" ];
      }

      return sonuç;
    }();

İsimsiz işlev, işaretlenmiş olarak gösterilen küme parantezleri arasında tanımlanmıştır ve sondaki işlev çağrı parantezleriyle işletilmektedir. Sonuçta meyveler değişkeni, istendiği gibi const olarak tanımlanabilmiştir.

shared static this (ve static this) özel ilkleme bloklarında const ve immutable değişkenlere doğrudan değer atanabilir. Bu bloklar özellikle modül düzeyinde (işlevlerin dışında) tanımlanmış olan değişkenlerin ilklenmeleri için kullanılırlar:

immutable int[] i;

shared static this() {
    // 'const' ve 'immutable' modül değişkenleri bu blok içinde
    // değiştirilebilirler:
    i ~= 43;

    // Değişkenler programın geri kalanında yine de 'const' veya
    // 'immutable' olarak tanımlanmış gibi kullanılırlar.
}

shared static this blokları main işlevinden önce işletilirler.

Bütün dilime karşılık elemanlarının değişmezliği

const bir dilimin türünün .stringof ile const(int[]) olarak yazdırıldığını yukarıda gördük. const'tan sonra kullanılan parantezlerden anlaşılabileceği gibi, değişmez olan dilimin bütünüdür; o dilimde hiçbir değişiklik yapılamaz. Örneğin dilime eleman eklenemez, dilimden eleman çıkartılamaz, var olan elemanların değerleri değiştirilemez, veya dilimin başka elemanları göstermesi sağlanamaz:

    const int[] dilim = [ 1, 2 ];
    dilim ~= 3;                    // ← derleme HATASI
    dilim[0] = 3;                  // ← derleme HATASI
    dilim.length = 1;              // ← derleme HATASI

    const int[] başkaDilim = [ 10, 11 ];
    dilim = başkaDilim;            // ← derleme HATASI

Değişmezliğin bu derece ileri götürülmesi bazı durumlara uygun değildir. Çoğu durumda önemli olan, yalnızca elemanların değiştirilmeyecekleri güvencesidir. Dilim nasıl olsa elemanlara erişim sağlayan bir olanak olduğundan o elemanlar değiştirilmedikleri sürece dilimin kendisinde oluşan değişiklikler bazı durumlarda önemli değildir.

Bir dilimin yalnızca elemanlarının değişmeyeceği, const'tan sonraki parantezin yalnızca elemanın türünü içermesi ile sağlanır. Yukarıdaki kod buna uygun olarak değiştirilirse artık yalnızca elemanı değiştiren satır derlenemez; dilimin kendisi değiştirilebilir:

    const(int)[] dilim = [ 1, 2 ];
    dilim ~= 3;                    // şimdi derlenir
    dilim[0] = 3;                  // ← derleme HATASI
    dilim.length = 1;              // şimdi derlenir

    const int[] başkaDilim = [ 10, 11 ];
    dilim = başkaDilim;            // şimdi derlenir

Birbirlerine çok yakın olan bu söz dizimlerini şöyle karşılaştırabiliriz:

    const int[]  a = [1]; /* Ne elemanları ne kendisi
                             değiştirilebilen dilim */

    const(int[]) b = [1]; /* Üsttekiyle aynı anlam */

    const(int)[] c = [1]; /* Elemanları değiştirilemeyen ama
                             kendisi değiştirilebilen dilim */

Daha önceki bölümlerde bu konuyla üstü kapalı olarak karşılaştık. Hatırlarsanız, dizgi türlerinin asıl türlerinin immutable olduklarından bahsetmiştik:

Benzer şekilde, dizgi hazır değerleri de değişmezdirler:

Bunlara bakarak D dizgilerinin normalde immutable karakterlerden oluştuklarını söyleyebiliriz.

const ve immutable geçişlidir

Yukarıdaki a ve b dilimlerinin kod açıklamalarında da değinildiği gibi, o dilimlerin ne kendileri ne de elemanları değiştirilebilir.

Bu, ilerideki bölümlerde göreceğimiz yapılar ve sınıflar için de geçerlidir. Örneğin, const olan bir yapı değişkeninin bütün üyeleri de const'tır ve immutable olan bir yapı değişkeninin bütün üyeleri de immutable'dır. (Aynısı sınıflar için de geçerlidir.)

.dup ve .idup

Karakterleri değişmez olduklarından dizgiler işlevlere parametre olarak geçirilirken uyumsuz durumlarla karşılaşılabilir. Bu durumlarda dizilerin .dup ve .idup nitelikleri yararlıdır:

Örneğin, parametresinin programın çalışması süresince kesinlikle değişmeyecek olmasını isteyen ve bu yüzden onu immutable olarak belirlemiş olan bir işlevi .idup ile alınan bir kopya ile çağırmak gerekebilir:

void foo(string dizgi) {
    // ...
}

void main() {
    char[] selam;
    foo(selam);                // ← derleme HATASI
    foo(selam.idup);           // ← derlenir
}
Nasıl kullanmalı
Özet