const ve immutable
Ddili'nin makaleler bölümünde bu konuyu anlatan "const ve immutable Kavramları" isimli bir çeviri var. O makale, bu kavramları başka programlama dillerinden, ve özellikle C++'dan tanıyan kişilere yönelik olarak yazılmış. Eğer böyle bir deneyiminiz varsa, o makale sizin için yeterli olabilir. Ben bu derste bu kavramları yalnızca şimdiye kadar öğrendiklerimize bağlı olarak göstereceğim.
Değişkenler, programda geçen kavramları temsil ederler. Somut veya soyut, her türlü kavramı bir değişken olarak tanımlarız. Temsil ettikleri kavramlar arasındaki etkileşimleri de 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ınca, değişkenler olmadan olmaz: değişebilme kavramı, programların herhangi bir iş yapabilmeleri için kesinlikle gereklidir.
Buna rağmen, bu esnekliğin uygun olmadığı durumlar da vardır:
- bazı kavramlar zaten değişmezdirler; örneğin haftadaki gün sayısı 7'dir, matematikteki pi sayısının değeri bellidir, bir programın desteklediği dil sayısı programın çalıştığı sürece değişmeyecektir (örneğin Türkçe ve İngilizce'dir), vs.
- koddaki işlemlerin her birisinin her değişkeni değiştirebilecek kadar esnek olması; hangi işlemlerin hangi değişkenleri değiştirdiklerini fazla serbest bıraktığı için, kodun okunması ve geliştirilmesi güç bir duruma gelir.
Örneğin emekliEt(banka, çalışan) gibi bir işlev çağrısı sonucunda çalışan'ın banka'dan emekli edildiğini anlayabiliriz. Ancak, bu işlevden dönüldüğünde bu iki değişkenden hangilerinin değişmiş olabileceklerini bilmek de önemlidir; yoksa her işlev çağrısına şüpheyle bakmaya başlarız.
Herhalde banka'nın eleman sayısı azalacaktır; peki çalışan değişkeni de değişecek midir; örneğin bu işlev çalışan değişkeninin içindeki bir enum değişkeni de ÇalışmaDurumu.emekli olarak değiştirecek midir?
Kodun içinden çıkılmaz derecede güç bir duruma gelmemesi için bazı durumlarda değişmezlik olgusundan yararlanmak isteriz. Bazı kavramların bazı işlemler sırasında değişmeyecekleri güvencesine gerek duyarız.
D'deki değişmezlik kavramlarını gösteren iki anahtar sözcük İngilizce'de de çok yakın anlamdadırlar: 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. Sonuçta ikisi de "değişmez" anlamına geliyor olsalar da, programda görevleri farklıdır ve bazı durumlarda birbirleriyle uyumsuzdurlar.
Bu uyumsuzluğa önceki derslerde rastladık: kendisi aslında immutable(char)[]'ın takma ismi olan string, değişebilen char[] dizgileriyle uyumlu değildir.
const
Bu anahtar sözcük bir değişkene sabitmiş gibi davranılacağını, yani o değişkenin değiştirilmeyeceğini belirler. Tanımlandığı sırada tür isminden önce const yazılan değişkenler, ilk değerleri verildikten sonra artık değiştirilemezler.
Şimdiye kadar hep değiştirilebilen kavramlar gördük:
int birDeğer = birHesap(); // ... daha sonra: birDeğer = başkaHesap(); // ← çalışır; değişebilir
Aynı kavramı const olarak tanımlamak, derleyicinin onun değiştirilmesine izin vermemesini sağlar:
const int birDeğer = birHesap(); // ... daha sonra: birDeğer = başkaHesap(); // ← derleme HATASI
Bu, programcı için yararlı bir kısıtlamadır. Değişkenin belirli bir kapsam içinde değişmeyecek olduğunu bilmek, kodun anlaşılmasını çok kolaylaştırır. Olası hataları da önler: değişmeyecek olduğu düşünüldüğü için const olarak belirlenmiş olan bir değişkeni sonradan değiştirmeye çalışmaya derleyici izin vermez. O noktada programcı ya yanlışlığı farkeder, ya da tasarımı gözden geçirerek const'ın kaldırılması gerektiğine karar verir.
Bir örnek olarak, bir fiyatıHesapla işlevine bakalım. Bu işlev, fiyat listesi olarak bir double dizisi alıyor ve dizideki bütün fiyatların toplamlarını döndürüyor olsun:
import std.cstream; double fiyatıHesapla(double[] fiyatListesi) { double toplam = 0; foreach (birimFiyatı; fiyatListesi) { toplam += birimFiyatı; } return toplam; } void main() { double[] fiyatListesi = [ 10.25, 5.50, 3.00 ]; // ... sonra başka fiyat da eklenebilir double toplamFiyat = fiyatıHesapla(fiyatListesi); dout.writefln("Toplam fiyat: ", toplamFiyat); }
fiyatıHesapla işlevinin, kendisine verilen fiyat listesinde bir değişiklik yapması mantıklı olmaz. Onun işi, listedeki birimlerin fiyatlarını ekleyerek toplam fiyatı döndürmek olmalıdır. Oysa, parametresini const olarak almadığı için, fiyatListesi'nin değişmesi için bir engel yoktur. Belki de bir yanlışlık sonucu olarak eklenebilecek bir satır, fiyatListesi'nin değişmesine neden olur:
double fiyatıHesapla(double[] fiyatListesi) { fiyatListesi[0] = 1234.56; // ← sorunsuz derlenir
fiyatıHesapla işlevini çağıran kapsamlar, verilen parametrenin değişmediğinden emin olamazlar. Bu, programcının aklında tutması gereken bir bilgidir: bugün değiştirmiyorsa bile, ilerideki bir kod geliştirme sonucunda parametre değişebilir.
Bu sakıncayı ortadan kaldırmak için, parametrenin değişmeyeceğini const anahtar sözcüğü ile belirtmek gerekir. Böylece parametrenin değişmesini hem derleyici önleyecektir, hem de programcılar işleve verilen parametrenin değişmediğinden emin olabileceklerdir. Bu kod artık derlenemez:
double fiyatıHesapla(const double[] fiyatListesi) { fiyatListesi[0] = 1234.56; // ← derleme HATASI
Bu yararlı bir denetimdir. Fiyat hesaplayan bir işlevin listede değişiklik yapması olsa olsa bir hata olacağı için; derleyicinin sağladığı bu güvenceden yararlanmak önemlidir.
Devam edersek; eğer aynı örnekteki toplamFiyat'ın bir kere ilklendikten sonra bir daha değişmeyeceği düşünülüyorsa, o da const olarak tanımlanabilir:
const double toplamFiyat = fiyatıHesapla(fiyatListesi);
toplamFiyat da artık değiştirilemez.
const değişken, const olmayan parametre yerine kullanılamaz
Değişmeyecek olduğu halde const olarak tanımlanmayan bir parametre, işlevin kullanışlılığını düşürür. Bunu görmek için tekrar fiyatıHesapla işlevine dönelim ve parametresinin en baştaki gibi yanlış olarak const olmadan tanımlandığını varsayalım:
double fiyatıHesapla(double[] fiyatListesi) {
O işlev, programın ilk gösterdiğim halinde de zaten öyleydi. O haliyle derlenir ve program doğru olarak çalışır.
Şimdi o işlevin çağrılmasından hemen önceye dönelim. main içindeki fiyatListesi'nin değişmeyecek olduğunu, ve bu durumda programcının onu doğru olarak const ile tanımladığını varsayalım:
const double[] fiyatListesi = [ 10.25, 5.50, 3.00 ]; // ... artık başka fiyat eklenemez double toplamFiyat = fiyatıHesapla(fiyatListesi); // ← derleme HATASI
Program artık derlenemez. main içinde const olarak tanımlanan, yani değişmeyecek olan bir dizinin, onu değiştirmeyeceği güvencesini vermeyen bir işleve parametre olarak gönderilmesine derleyici izin vermez. Sonuçta, işlev parametresinin const olarak tanımlanmamış olması, işlevin kullanışlılığını düşürmektedir, ve işlevin ancak const olmayan dizilerle kullanılabilmesine neden olmaktadır.
const'ın, mantıklı olan her yerde kullanılması gerekir. const'ın yalnızca programcıya yardımcı bir olanak olarak değil, programın tutarlılığı açısından dikkat edilmesi gereken bir olgu olarak kabul edilmesi gerekir.
Not: const'ın doğru olarak kullanılmasıyla ilgili olan bu konunun İngilizce'si "const correctness"tır.
const değişken, kesinlikle değişmez değildir
const, değişkenin "bu kapsam içinde" veya "bu parametre yoluyla" değiştirilemeyeceğini belirler. Oysa değişkenin kendisi değişmez olmayabilir. Bunu yukarıdaki fiyatıHesapla işlevinin parametresinde görmüştük: değişken, parametre const olduğu için, işlev içinde değiştirilemiyordu; oysa main içindeki fiyatListesi yine de değiştirilebiliyordu.
Bunu değişik bir örnekle tekrar görelim. Herhangi bir değişkeni çıkışa yazdırmak için kullanılacak olan bir işlevin, yazdıracağı parametrede değişiklik yapmasını beklemeyiz. Yoksa hem sürprizlerle karşılaşırız, hem de son gördüğümüz derleme hatasında olduğu gibi, const değişkenleri o işlevle yazdıramayız. Bu yüzden, bir öğrencinin notlarını yazdıran bir işlevin parametrelerini const olarak tanımlamak doğru olur:
void notlarıYazdır(const char[] isim, const int[] notlar) { dout.writefln("Öğrenci: ", isim); dout.writef("Notlar :"); foreach (not; notlar) { dout.writef(' ', not); } dout.writefln(); }
Oysa örneğin notlar, o işlevi çağıran tarafta değişebiliyor olabilir:
int[] notlar = [ 80, 95, 90 ];
notlarıYazdır(isim, notlar);
notlar[0] = 81;
notlarıYazdır(isim, notlar);
Öğrenci: Mehmet Notlar : 80 95 90 Öğrenci: Mehmet Notlar : 81 95 90
Özetle, const, bir değişkenin "burada değişmeyeceği" veya "bu isimle değişmeyeceği" gibi bir anlam taşır. notlarıYazdır işlevinin parametresi const olduğu için notlar orada değiştirilemez; ama işlevin çağrıldığı kapsamda int[] olarak tanımlandığı için değişebilir.
immutable
Bu anahtar sözcük, değişkenin değişmezliği konusunda const'tan daha kuvvetli bir anlama sahiptir. const'tan farklı olarak, bir değişkenin programın çalışması sırasında kesinlikle değişmeyeceğini bildirir.
Örneğin haftanın gün isimlerini taşıyan bir dizi hiçbir zaman değişmez; kullanıcıya üç deneme şansı veren bir oyunda deneme sayısı sınırı değişmez; programın gösterdiği menü seçenekleri değişmezdir, vs. Bu tür değişkenler immutable ile tanımlanırlar:
immutable string günİsimleri[] = [ "Pazartesi", "Salı", "Çarşamba", "Perşembe", "Cuma", "Cumartesi", "Pazar" ]; günİsimleri[0] = "İlk gün"; // ← derleme HATASI immutable int denemeSayısıSınırı = 3; denemeSayısıSınırı = 4; // ← derleme HATASI
const değişken, immutable parametre yerine kullanılamaz
Her ikisi de değişmezliği belirliyor olsalar da, const değişkenler işlevlere immutable parametre olarak gönderilemezler:
void immutableAlanİşlev(immutable int[] birDizi) { // ... } void main() { const int[] constDizi; immutable int[] immutableDizi; immutableAlanİşlev(constDizi); // ← derleme HATASI immutableAlanİşlev(immutableDizi); // ← çalışır }
Bu konunun benzeriyle daha önceki derslerde de karşılaştık. Hatırlarsanız, dizgi türlerinin takma isimlerinin asıl türlerinin immutable olduklarından bahsetmiştik:
immutable(char)[]'ın takma ismistringimmutable(wchar)[]'ın takma ismiwstringimmutable(dchar)[]'ın takma ismidstring
Ek olarak, dizgi hazır değerleri de değişmezdirler:
- "merhaba"c hazır dizgisinin türü
string'dir - "merhaba"w hazır dizgisinin türü
wstring'dir - "merhaba"d hazır dizgisinin türü
dstring'dir
.dup ve .idup
Bu yüzden, dizgileri işlevlere parametre olarak geçirirken uyumsuz durumlarla karşılaşabiliriz. Bu durumlarda dizgilerin .dup ve .idup niteliklerinden yararlanmamız gerekebilir:
.dupdizginin değişebilen bir kopyasını oluşturur; ismi, "kopyasını al" anlamındaki "duplicate"ten gelir.idupdizginin değişmez bir kopyasını oluşturur; ismi "immutable duplicate"ten gelir
Örneğin, değişebilen bir dizgi alan bir işlevi bir string ile doğrudan çağıramayız. Bu durumda işlevi .dup ile aldığımız kopya ile çağırmamız gerekir:
void foo(char[] dizgi) { // ... } void main() { foo("merhaba"); // ← derleme HATASI foo("merhaba".dup); // ← çalışır }
Parametresinin programın çalışması süresince kesinlikle değişmeyecek olmasını isteyen ve bu yüzden de onu immutable olarak belirlemiş olan bir işlevi de .idup ile çağırmamız gerekebilir:
void foo(string dizgi) { // ... } void main() { char[] selam; foo(selam); // ← derleme HATASI foo(selam.idup); // ← çalışır }
Nasıl kullanmalı
Değişken tanımlarken, programın çalışması sırasında kesinlikle değişmeyecek olan değerleri immutable olarak tanımlayın. Örneğin bir dakikadaki toplam saniye sayısı değişmeyecektir:
immutable int dakikaBaşınaSaniye = 60;
Parametre tanımlarken; eğer parametrede bir değişiklik yapılmayacaksa, parametreyi const olarak tanımlayın. Öyle yaptığınızda parametreyi değiştirmeme sözü verdiğiniz için; değişebilen, const, ve immutable değişkenleri o işleve gönderebilirsiniz:
void foo(const char[] dizgi) { // ... } void main() { char[] değişebilenDizgi; const char[] constDizgi; string immutableDizgi; foo(değişebilenDizgi); // ← çalışır foo(constDizgi); // ← çalışır foo(immutableDizgi); // ← çalışır }
Eğer parametrede bir değişiklik yapacaksanız, o parametreyi değişebilen şekilde tanımlayın; zaten const veya immutable tanımladığınızda değiştiremezsiniz:
import std.cstream; void tersÇevir(char[] dizgi) { const int uzunluk = dizgi.length; for (int i = 0; i != uzunluk / 2; ++i) { const char geçici = dizgi[i]; dizgi[i] = dizgi[uzunluk - 1 - i]; dizgi[uzunluk - 1 - i] = geçici; } } void main() { char[] selam = "merhaba".dup; tersÇevir(selam); dout.writefln(selam); }
abahrem
Genel bir kural olarak, olabildiği kadarıyla hep const tarafta kalmaya çalışın. Örneğin işlevin içinde değiştirilmeyen parametreleri const olarak tanımlayın.
Geçerli bir nedeni yoksa, işlev parametrelerinde immutable kullanmayın. Yoksa işlevinizin kullanışlılığı düşer.
Bundan sonraki bölümlerdeki örneklerde ben de bol bol const ve immutable kullanacağım.
D.ershane
Forum
Wiki
Projeler
Tanıtım
İletişim
Hakları