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

Değişkenlerin programla ilgili olan kavramları temsil ettiklerini gördük. Temsil ettikleri kavramlar arasındaki etkileşimleri bu değişkenlerin değerlerini değiştirerek sağlarız:

    // Parayı öde
    toplamFiyat = fiyatıHesapla(fiyatListesi);
    cüzdandakiMiktar -= toplamFiyat;
    bakkaldakiMiktar += toplamFiyat;

Bu açıdan bakıldığında değişkenler olmadan olmaz; değişebilme kavramı programların iş yapabilmeleri için önemlidir. Buna rağmen, değişimin uygun olmadığı durumlar da vardır:

Bazı kavramların bazı işlemler sırasında değişmeyecekleri güvencesine gerek duyarız. Bazı başka dillerde bulunmayan bu kesinlikle değişmezlik kavramı hata çeşitlerinden bazılarının olasılığını düşüren yararlı bir olanaktır.

Değişmezlik kavramlarını belirleyen iki anahtar sözcüğün İngilizce'deki anlamları da birbirlerine çok yakındır: const, "sabit, değişmez" anlamına gelen "constant"ın kısasıdır. immutable ise, "değişebilen" anlamına gelen "mutable"ın karşıt anlamlısıdır. İkisi de "değişmezlik" anlamını taşıyor olsalar da, immutable ve const sözcüklerinin görevleri farklıdır ve bazı durumlarda birbirleriyle uyumsuzdurlar.

const, immutable, inout, ve shared tür nitelendiricisidirler. (inout ve shared anahtar sözcüklerini ilerideki bölümlerde göreceğiz.)

Değişmezler

Her ne kadar kulağa anlamsız gelse de, bu başlık yerine "Değişmez değişken" de düşünebilirdi. Böyle anlamsız ifadeler İngilizce kaynaklarda da bulunur: "constant variable" veya "immutable variable" da kulağa aynı derecede yanlış gelen terimlerdir.

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ının sonuçları ile 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();
}

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

enum değişkenler işlev çağrılarının sonuçları ile de ilklenebilirler:

    enum a = diziYap();    // derleme zamanında çağrılır

Bu, D'nin derleme zamanında işlev işletme (CTFE) olanağı sayesindedir. Bu olanağı ilerideki bir bölümde göreceğiz.

immutable değişkenler

Bu anahtar sözcük de programın çalışması sırasında değişkenin kesinlikle değişmeyeceğini bildirir. enum'dan farklı olarak, immutable olarak işaretlenen değişkenlerin değerleri çalışma zamanında da hesaplanabilir.

Aşağıdaki program enum ve immutable 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 bilinemediği için 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 immutable 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;

    immutable 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) {
        immutable tahmin = sayıOku("Tahmininiz");
        doğru_mu = (tahmin == sayı);
    }

    writeln("Doğru!");
}

Gözlemler:

Program içinde açıkça immutable(int) diye parantezle yazılması gerekmese de immutable 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() {
    immutable      çıkarsanarak = 0;
    immutable int  türüyle      = 1;
    immutable(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 immutable'ı da içerir ve parantezlidir:

immutable(int)
immutable(int)
immutable(int)

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

const değişkenler

Bu anahtar sözcüğün değişkenler üzerinde immutable'dan bir farkı yoktur. const değişkenler de değiştirilemezler:

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

Değişkenlerin değişmezliğini belirlerken const yerine immutable kullanmanızı öneririm çünkü immutable değişkenler, parametrenin özellikle immutable olmasını isteyen işlevlere de gönderilebilirler. Bunu aşağıda göreceğiz.

Değişmez parametreler

İşlevlerin parametrelerinde değişiklik yapmayacakları sözünü vermeleri ve derleyicinin bunu garanti etmesi programcılık açısından çok yararlıdır. Bunun nasıl sağlanacağına geçmeden önce dilim elemanlarının işlevler tarafından değiştirilebildiklerini görelim.

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ıyorum. 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:

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 elemanları değiştirmek için yazılmışlardır.

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

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

Derleme hatası, immutable(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 (immutable(int[]))
const parametreler

immutable 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 başka işlevlere gönderilememeleri büyük bir kısıtlama olarak görülmelidir:

import std.stdio;

void main() {
    immutable 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 değiştirilemiyor olmaları onların yazdırılmalarına engel olmamalıdır. const parametreler bu konuda yararlıdırlar.

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 değişkenler hem de immutable değişkenler işlevlere const parametreler olarak gönderilebilirler:

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

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

İşlev tarafından değiştirilmediği halde const olarak tanımlanmayan bir parametre işlevin kullanışlılığını düşürür. Ek olarak, işlev parametrelerinin const olarak işaretlenmeleri programcı açısından yararlı bir bilgidir. Değişkenin belirli bir kapsam içinde değişmeyecek olduğunu bilmek kodun anlaşılmasını kolaylaştırır. Olası hataları da önler: Değişmeyecek olduğu düşünüldüğü için const olarak tanımlanmış olan bir değişkeni sonradan değiştirmeye çalışmaya derleyici izin vermez:

void yazdır(const int[] dilim) {
    dilim[0] = 42;    // ← derleme HATASI

O durumda programcı ya yanlışlığı farkeder ya da tasarımı gözden geçirerek const'ın kaldırılması gerektiğine karar verir.

const parametrelerin hem değişebilen hem de 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.

immutable parametreler

const parametre yerine kullanılan asıl değişkenin değişebilen veya immutable olabildiğini gördük. const parametreler bu anlamda esneklik getirirler.

Parametrenin immutable olarak işaretlenmesi ise asıl değişkenin de kesinlikle 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?

Yukarıdaki iki başlığa 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.

const belirteci asıl değişkenin değişebilen mi 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, 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ği için foo'nun const parametresinin 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);
}

Kod artık derlenebiliyor olsa da asıl değişkenin zaten immutable olduğu durumda bile kopyasının alınıyor olması gereksizdir.

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ı olabilirler. Aşağıdaki kodları kitabın bu aşamasında anlamanızı beklemesem de parametrenin const veya immutable olması kararını ortadan kaldırdığını belirtmek istiyorum. Aşağıdaki foo hem değişebilen hem de immutable değişkenlerle çağrılabilir ve yalnızca asıl değişken değişebilen olduğunda kopya bedeli öder:

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));
}
Bütün dilime karşılık elemanlarının değişmezliği

immutable bir dilimin türünün .stringof ile immutable(int[]) olarak yazdırıldığını yukarıda gördük. immutable'dan 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:

    immutable int[] değişmezDilim = [ 1, 2 ];
    değişmezDilim ~= 3;                    // ← derleme HATASI
    değişmezDilim[0] = 3;                  // ← derleme HATASI
    değişmezDilim.length = 1;              // ← derleme HATASI

    immutable int[] değişmezBaşkaDilim = [ 10, 11 ];
    değişmezDilim = değişmezBaş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, immutable'dan 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:

    immutable(int)[] değişmezDilim = [ 1, 2 ];
    değişmezDilim ~= 3;                    // şimdi derlenir
    değişmezDilim[0] = 3;                  // ← derleme HATASI
    değişmezDilim.length = 1;              // şimdi derlenir

    immutable int[] değişmezBaşkaDilim = [ 10, 11 ];
    değişmezDilim = değişmezBaşkaDilim;    // şimdi derlenir

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

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

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

    immutable(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 değiştirilemeyen 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