İngilizce Kaynaklar

Diğer



D'nin Saflık Kavramı

Yazar: David Nadlinger
Çevirmen: Ali Çehreli
Çeviri Tarihi: Ekim 2013
İngilizcesi: Purity in D

Programlama dillerinin tasarımları tartışmaya açık konulardır. Buna rağmen, hem donanımda yaşanan gelişmelerin hem de belirli bir dilde yazılmış olan programların bakımlarının önemli konular olmaları nedeniyle daha önceden fonksiyonel programlamada ortaya çıkmış olan kavramlar yeni dillerde tekrardan keşfedilmektedirler. D programlama dili de fonksiyonel saflık konusuna kendi yaklaşımını getirir. Bu yazı D'nin pure (saf) anahtar sözcüğünü tanıtmakta ve onun diğer olanaklarla nasıl etkileştiğini göstermektedir.

Saflık kavramı hem programcının hem de derleyicinin kaynak kodu anlama konusunda yararlandıkları güçlü bir araçtır. Bu olanağın yararlarına ve asıl kullanımlarına geçmeden önce pure anahtar sözcüğünün D'de tam olarak ne anlama geldiğine bakalım. Eğer bu kavramı başka dillerden tanıyorsanız lütfen bir süreliğine hiç duymamış gibi davranın. Bu kavramın D'deki gerçekleştirmesindeki küçük farkların büyük etkileri olduğunu göreceksiniz.

pure, bir işlevi çağıran ile o işlev arasında bir sözleşme belirleyen bir niteliktir. Saf bir işlev değişebilen (mutable) evrensel değişkenleri kullanamaz. Burada "evrensel" ile kastedilen, işlevin parametreleri dışındaki tüm değerlerdir (işlevin parametreleri de iş parçacıkları arasında paylaşılabilen (shared) veriler olamazlar). Ek olarak, burada "kullanamaz" sözü ile kastedilen, o değişkenlere okumak için de yazmak için de erişilemediğidir. Bunun tersi olarak, pure olarak işaretlenmemiş olan bir işlev de saf değil (impure) olarak adlandırılır.

Yukarıdaki tanımın anlamı, saf bir işlevin aynı parametre değerlerine (argument) karşılık olarak hep aynı yan etkiyi üreteceği ve aynı değeri döndüreceğidir. Dolayısıyla, saf bir işlev saf olmayan işlevleri çağıramaz ve klasik anlamda giriş ve çıkış işlemleri uygulayamaz.

Saflık kavramının fazla kısıtlayıcı olmasını engellemek adına, aslında izin verilmemesi gereken bazı yan etkiler yine de mümkün bırakılmıştır (isterseniz bu ayrıntıları atlayabilirsiniz):

Referans saydamlığı (referential transparency)

Fonksiyonel programlama dili dünyasında çok yaygın olarak geçen parametrelerin değişmezliği (immutability) kavramının yukarıdaki tanımda geçmiyor olması size şaşırtıcı gelmiş olabilir. D'de saf işlevler parametrelerinde değişiklik yapabilirler. Örneğin, aşağıdaki kod D'de yasaldır:

int okuVeArttır(ref int x) pure {
  return x++;
}

Programlama dilleri açısından bakıldığında saflık kavramı normalde referans saydamlığını da içerir. Bunun anlamı, saf bir işlev çağrısı yerine o çağrının dönüş değerinin yerleştirilmesi sonucunda programın davranışında hiçbir değişiklik olmayacağıdır. Bu, D'nin varsayılan davranışı değildir. Örneğin, aşağıdaki kod

int değer = 1;
auto sonuç = okuVeArttır(değer) * okuVeArttır(değer);
// assert(değer == 3 && sonuç == 2);

okuVeArttır'ın tek kere çağrılması ile aynı sonucu doğurmaz:

int değer = 1;
auto geçici = okuVeArttır(değer);
auto sonuç = geçici * geçici;
// assert(değer == 2 && sonuç == 1);

Bir sonraki başlıkta da göreceğimiz gibi, bu aslında emirli dillerde çok aranan bir özelliktir. Buna rağmen, saflık kavramının klasik tanımının verdiği garantiler ve bunun getirdiği yararlar da göz ardı edilmemelidir. Bu konuda D'nin tür sisteminin başka bir özelliğinden yararlanılır: Veriye erişimin geçişli olarak const olarak işaretlenebilmesi veya verinin en başından immutable olarak tanımlanabilmesi3. Bu konuyu biraz daha yakından incelemek için aşağıdaki üç işlev bildirimine bakalım:

int a(int[] val) pure;
int b(const int[] val) pure;
int c(immutable int[] val) pure;

Parametresi değişebilen türden olan a'nın durumu okuVeArttır'ınkinin aynısıdır (int[] türü bir gösterge ve bir uzunluktan oluşan ve belleğin bir bölgesine erişim sağlamaya yarayan bir dinamik dizidir). b ve c ise çok daha iyi durumdadırlar: Saf olduklarından bu işlevlerin programın evrensel durumunu okuyamadıklarını ve evrensel durumunda değişiklik yapamadıklarını biliyoruz. Dolayısıyla, b ve c işlevlerinin kelimenin yaygın anlamı ile yan etkileri olmadığını ve bu yüzden bu işlevlere yapılan çağrıların referans saydamlığına sahip olduklarını söyleyebiliriz.

Peki, b ile c arasındaki bir farklılıktan söz edilebilir mi? Saflık kavramı açısından hayır, çünkü const ve immutable işlevin parametre üzerindeki hakları konusunda aynı anlama gelir. (immutable ayrıca verinin kesinlikle değişmeyeceği garantisini de verir ama işlev saf olduğundan bu parametreye erişim sağlayan referanslar zaten işlevden dışarıya sızdırılamazlar (dönüş değeri dışında).)

Yine de, işlevi çağıranı ilgilendiren ince ama önemli bir farktan söz edilebilir: Bu fark, parametre değerinin gerçekte yalnızca const mı yoksa immutable mı olduğuna bağlıdır. Bunun nedeni, const'ın hem değişebilen hem immutable değerlere erişim sağlayabilmesine karşın immutable'ın yalnızca immutable olan veya immutable'a dönüşebilen değerlere erişim sağlayabilmesidir. Dolayısıyla, aşağıda yazılanlar immutable bir dizi ile çağrılan durumda hem b için hem de c için aynen geçerlidir.

Örnek olarak, ifade değerlerini hatırlamaya (memoization) veya alt ifadelerin azaltılmalarına dayanan eniyileştirme yöntemlerini düşünelim. pure bir işleve aynı immutable değerlerle yapılan birden fazla çağrının teke indirilebilmesi için o değerlerin yalnızca kimliklerine bakılması yeterlidir. (Örneğin, yalnızca o değerlerin adreslerine bakmak gibi bir kaç basit karşılaştırma yaparak.) Öte yandan, const olan bir parametre değerinin değişebilen referanslar içerdiği durumda o referans iki çağrı arasında başka kodlar tarafından değiştirilebileceğinden, o referansların çalışma zamanında derinlemesine incelenmeleri veya derleyicinin program akışını derleme zamanında anlayabilmesi gerekir.

Aynı durum koşut işlemleri de etkiler: pure bir işlevin parametre değerlerinin referans içermediği veya yalnızca immutable referans içerdiği durumlarda işlemlerin koşut olarak işletilmelerinde bir sakınca yoktur çünkü o işlev programın davranışında belirsizliklere neden olabilecek yan etkiler oluşturamaz; ek olarak, parametre değerlerine erişim konusunda yarış (data race) halinde de olunamaz. Ne var ki, const parametreleri inceleyerek aynı tür sonuçlara varılamayabilir çünkü aynı değerlere değişebilen çeşitten erişimi olan kodlar onları değiştirebilirler.

Dönüş türünün referans içermesi

Parametrelerinin değişebilme açısından farklılıklar göstermelerine karşın, yukarıdaki örneklerdeki a, b, ve c işlevlerinin üçünün de dönüş türü bu gibi örneklerde sık kullanılan int idi. Peki, saf bir işlevin referans içeren bir tür döndürdüğü durumlarda neler önemli olabilir?

İlk önemli konu, referans saydamlığı göze alınırken eşitlik karşılaştırmalarında yararlanılan adreslerdir. Genelde fonksiyonel dillerde bir verinin belleğin hangi noktasında bulunduğunun önemi ya hiç yoktur ya da çok azdır. Bir sistem dili olan D bu kavramı da kullanıma sunar. Örneğin, bir dizi oluşturan ve o diziyi ilk adet adet asal sayı ile dolduran ulong[] asallar(uint adet) pure işlevine bakalım. asallar aynı adet değeri ile her çağrıldığında aynı sonucu üretiyor olsa da, o sonuçları içeren diziler her seferinde farklı adreslerde bulunacaklardır. Dolayısıyla, dönüş değerlerinde referans içeren işlevlerin referans saydamlığı göze alınırken bit düzeyindeki eşitlik kavramı (is) değil, mantıksal anlamdaki eşitlik kavramı (==) önemlidir.

Referans saydamlığındaki ikinci önemli konu ise dönüş türünün değişebilen referanslar içerip içermediğidir. asallar işlevini kullanan aşağıdaki koda bakalım:

auto p = asallar(42);
auto q = asallar(42);
p[] *= 2;

asallar'a yapılan yukarıdaki ikinci çağrı yerine q = p işleminin gelemeyeceği açıktır çünkü o durumda q da p ile aynı bellek bölgesine erişim sağlayacağından çarpma işleminden sonra o da sonuçların iki katlarına sahip olmaya başlayacaktır. Genel açıdan bakıldığında, dönüş türünde değişebilen referanslar içeren saf işlev çağrılarının referans saydamlığı taşıdığı söylenemez; buna rağmen, bazı çağrılar çağıran taraftaki kullanımlara bağlı olarak yine de eniyileştirilebilirler.

Yarı saflık ve getirdiği garantiler

D'nin pure anahtar sözcüğünün ilk tasarımı çok daha sıkı kurallara bağlıydı. Dil, o zamanlarda bu yazının başında verilen tanıma uyan tek saflık kavramı içeriyor idiyse de, sonuçta kabul edilen ve daha gevşek kurallar getiren bugünkü saflık kavramının tartışmaları sırasında iki terim ortaya çıkmıştı: Yukarıdaki okuVeArttır ve a işlevlerinde de görüldüğü gibi, değişebilen parametrelere sahip işlevleri tanımlayan yarı saf (weakly pure), ve b ve c işlevlerinde görüldüğü gibi, yan etkileri bulunmayan işlevleri tanımlayan tam saf (strongly pure). Ancak, bu terimler konusunda tam bir anlaşma bulunmamakta ve bunların forumlardaki kullanımları sürekli olarak karışıklıklara neden olmaktadır; bu terimleri saflık kavramı tasarımında ilk ortaya atan Don Clugston bile artık kullanılmamalarını istemektedir.

Buna rağmen, belki de parametre ve dönüş türlerine bağlı olarak verilen garantilerin farklılıkları nedeniyle bu terimler hâlâ kullanılmaktadır. Çok basit olmalarına rağmen belki de kurallarına yabancı kalındığından D'deki saflık kavramı tam olarak anlaşılamamaktadır. Peki, saf işlevlerin parametrelerini değiştirmelerine neden izin verilmektedir?

D'deki saflık kavramının arkasındaki asıl güç, kuralları böyle yumuşatmanın aslında daha fazla sayıda işlevin tam saf olabilmelerini sağlamasıdır. Bunu göstermek için id Software'den tanınan John Carmack'ın Functional Programming in C++ başlıklı #AltDevBlogADay yazısından bir bölüm aktarayım. Yazı, fonksiyonel programlama ilkelerinin C++'a uygulanmasının yararlarını gösterir:

Programcılıkta saf işlevler verinin daha fazla kopyalanmasına neden olur; program hızı açısından bakıldığında bunun yanlış bir gerçekleştirme yöntemi olduğu açıktır. Bir uç örnek olarak, ÜçgenÇiz gibi bir işlev parametre olarak bir çerçeve ara belleği (framebuffer) alır ve sonuç olarak içine üçgen çizilmiş olan yepyeni bir çerçeve ara belleği döndürür. Bunu yapmayın. — http://www.altdevblogaday.com/2012/04/26/functional-programming-in-c/

Yukarıdaki görüş doğrudur; çerçeve ara belleğinin her üçgen çizimi için tekrar kopyalanmasının iyi bir fikir olmadığı açıktır. Buna rağmen, üçgen çizen saf bir işlev D'de hızdan ödün vermeden de gerçekleştirilebilir! Böyle bir işlevin bildirimi aşağıdaki gibi olabilir:4

alias Renk = ubyte[4];
struct Köşe { float[3] konum; /* ... */ }
alias Üçgen = Köşe[3];
void üçgenÇiz(Renk[] çerçeve, const ref Üçgen üçgen) pure;

Bu kadarıyla iyi: Yukarıdaki alıntıda da belirtildiği gibi, üçgenÇiz'in referans saydamlığı taşıdığını söyleyemiyoruz çünkü çerçeve ara belleğine yazmak zorundadır. Yine de, pure belirteci sayesinde bu işlevin programın gizli veya evrensel durumunu kullanamadığından eminiz. Dahası, saf olması nedeniyle bu işlev başka saf işlevler tarafından da çağrılabilmektedir. Küçük örneğimize devam edersek, eğer her çerçeve için yeni bir ara bellek ayrılsaydı, üçgenlerden oluşan bir görüntüyü oluşturan bir işlev aşağıdaki gibi olurdu:

Renk[] sahneÇiz(
   const Üçgen[] üçgenler,
   ushort en = 640,
   ushort boy = 480
) pure {
   auto sahne = new Renk[en * boy];
   foreach (ref üçgen; üçgenler) {
      üçgenÇiz(sahne, üçgen);
   }
   return sahne;
}

sahneÇiz'in parametrelerinin değişebilen referanslar içermediklerine dikkat edin – parametrelerinde değişiklik yapan üçgenÇiz'i çağırdığı halde kendisi parametrelerinde değişiklik yapmamaktadır!

Bu her ne kadar zorlama bir örnek olsa da, emirli kod performansından ödün vermeyen D'de benzer durumlarla sık karşılaşılır (örneğin, değişebilen toplulukların saf işlevler içinde geçtikleri her durum). Bu konu saflık kavramının yukarıda anlatılan ilk tasarımından edinilen deneyimlere de uymaktadır – saflık kuralları yumuşatıldığında, aynı güçlü garantiler çelişkili bir biçimde daha çok sayıda kod için sağlanabilmektedir.

Çoğu modern kodlama standardı zaten evrensel değişkenlerden kaçınılmasını önerdiğinden, giriş çıkış işlemleri ile ilgilenmeyen çoğu D işlevi kolaylıkla pure olarak işaretlenebilir. Bu doğru olduğuna göre neden varsayılan belirteç pure olmamıştır ve diğer işlevlerin örneğin impure diye işaretlenmeleri gerekmemiştir? D'nin ikinci sürümü açısından bakıldığında bunun nedeni saflık kavramının son tasarımının dilin gelişmesinin sonlarına rastlaması ve böyle bir değişikliğin halihazırda yazılmış olan kodları etkileme riskinin fazla yüksek olmasıdır. Yine de bu, gelecekteki başka diller ve belki de D'nin bir sonraki ana sürümü açısından dikkate alınacak bir fikirdir.

Şablonlar ve saflık

Bu noktaya kadar saflık tasarımını başka olanaklardan bağımsız olarak ele aldık. Bundan sonraki başlıklarda öncelikle işlev şablonları olmak üzere saflığın diğer dil olanaklarıyla etkileşimlerini göreceğiz.

Bir işlev şablonunun tür parametrelerinin belirli türler için her kullanımı, sıradan bir işlevin eşdeğeridir. Bu yüzden saflık kavramı onlar için de yukarıda anlatıldığı gibidir. Buna rağmen, belirli bir şablonun saf olup olamadığı o şablonun kullanıldığı türlere bağlı olabildiğinden şablonların durumu biraz daha karışıktır.

Bunun bir örneği olarak bir aralık5 (range) alan ve o aralıktaki elemanlardan oluşan bir dizi döndüren bir işleve bakalım (bu işlev std.array modülünde zaten bulunur ve oradaki gerçekleştirmesi çok daha kullanışlıdır). Böyle bir işlev aşağıdaki gibi yazılabilir:

auto array(R)(R r) if (isInputRange!R) {
   ElementType!R[] sonuç;
   while (!r.empty) {
      sonuç ~= r.front;
      r.popFront();
   }
   return sonuç;
}

Yukarıdaki işlev, bir aralıktaki elemanları bir dizi olarak döndüren std.array.array'in fazla hızlı olmayan bir gerçekleştirmesidir. Bu işlev pure olabilir mi?

Bu işlevin nasıl işlediğini tahmin etmek güç olmasa gerek – aralığın başındaki eleman aralıktan çıkartılıyor ve dizinin sonuna ekleniyor, ve bu işlem aralıkta eleman kalmayana kadar sürdürülüyor. Soru: Bu işlev pure yapılabilir mi? R türü map veya filter gibi işlevlerin dönüş türü olduğunda bu kodun saf kodlar tarafından çağrılamaması için bir neden yoktur. Öte yandan, eğer R örneğin girişten satır okuma gibi bir işlemi de sarmalıyorsa r.empty, r.front, ve r.popFront'un hepsinin birden pure olmaları olanaksızdır. Dolayısıyla, array pure olarak işaretlense, böyle aralıklarla çalışamaz. Peki, ne yapılabilir?

Buna bir çözüm olarak pure belirtecini R'ye bağlı olarak koymayı sağlayan bir D söz dizimi değişikliği önerilmişti. Bu çözüm önerisi dile getireceği karmaşıklık ve neden olacağı aşırı kod tekrarı nedeniyle benimsenmedi. Sonuçta kabul edilen çözüm oldukça basittir: D'de şablon kodları nasıl olsa derleyici tarafından görülmek zorunda olduklarından şablonların saflığı derleyici tarafından otomatik olarak belirlenebilir (aynısı nothrow ve başka belirteçler için de geçerlidir).

Bunun anlamı, yukarıdaki örnekteki array'in buna izin veren aralık türleri için saf işlevler tarafından çağrılabilmesi, başka türler için ise çağrılamamasıdır. Saflığın şablon parametrelerine bağlı olmadığı durumda işlev şablonları en azından belgeleme açısından yine de açıkça pure olarak işaretlenebilir.

Saf üye işlevler

Doğal olarak, yapı ve sınıf üye işlevleri de saf olabilir. Normal işlevlerin saflık kuralları bir farkla onlar için de geçerlidir: Saflık kuralları üye işlevlerin gizli this parametresini de kapsar. Bunun anlamı, saf işlevlerin üye değişkenlere de erişebilecekleri ve onları da değiştirebilecekleridir.

class Foo {
  int getBar() const pure {
    return bar;
  }

  void setBar(int bar) pure {
    this.bar = bar;
  }

  private int bar;
}

Saf işlevler üye değişkenlere erişebilirler. (Not: Normalde getter/setter işlevleri yerine D'de nitelik işlevlerinden (property) yararlanılır. )

Bir üye işlev const veya immutable olarak işaretlendiğinde de o belirteç aslında this parametresine uygulanır. Dolayısıyla, yukarıda değişebilme konusunda anlatılanlar burada da geçerlidir.

Saflık, türeme konusunda da beklenebileceği gibidir: Genel olarak, bir alt sınıfın beklentileri üst sınıfınkilerden daha azdır; verdiği garantiler ise daha fazladır (dönüş türlerinin ortakdeğişkeliğinde (covariance) görüldüğü gibi). Dolayısıyla, saf olmayan bir işlevin alt sınıftaki tanımı saf olabilir ama bunun tersi doğru değildir. Bir kolaylık olarak da, saf olan bir üst sınıf işlevinin alt sınıftaki tanımının açıkça pure olarak işaretlenmesi gerekmez; böyle bir işlev otomatik olarak saftır (C++'taki virtual gibi). Walter Bright'ın bu konuda bir blog yazısı vardır.

pure ve immutable – bir kere daha

const ve immutable belirteçlerinin referans saydamlığına etkilerini yukarıda gördük. pure'un getirdiği garantiler bazı durumlarda bazı ek çıkarımlar da sağlar. Bunun tür sisteminin bir parçası da olan belirgin bir örneği, pure işlevlerin dönüş türlerinin bazı durumlarda güvenle immutable olarak kullanılabilmeleridir. Örnek olarak yine yukarıdaki ulong[] asallar(uint adet) pure işlevine bakalım. Aşağıdaki kodun nasıl olup da derlenebildiği ilk bakışta açık olmayabilir:

immutable ulong[] a = asallar(5);

immutable bir verinin başka hiçbir değişebilen referansı olamadığını biliyoruz. Buna rağmen, asallar'ın döndürdüğü ve değişebilen değerlerden oluşan dizi immutable olarak tanımlanabilmektedir. Peki, yukarıdaki kodun derlenebilmesinin nedeni nedir? Döndürdüğü değerleri değiştirebilecek olan başka referansların bulunamayacağı, bir işlevin pure olmasının sonucudur: Böyle bir işlev değişebilen referanslar içeren parametrelere sahip değildir ve değişebilen evrensel değişkenlere erişemez. Dolayısıyla, her ne kadar değişebilen değerlerden oluşan bir referans döndürüyor olsa da o işlevi çağıranlar o değerlerin değişemeyeceklerinden emindirler.

Her ne kadar fazla önemsiz bir ayrıntıymış gibi görünse de bu aslında şaşırtıcı derecede kullanışlı bir olanaktır çünkü hem işlevlerin fonksiyonel programlamada olduğu gibi değişmez veriler kullanabilmelerini, hem de verinin hız nedeniyle olduğu yerde değiştirildiği geleneksel kodlarda gereksiz veri kopyalarının önüne geçilmesini sağlamaktadır.

Peki, kaçış kapısı nerede?

Saf bir işlevin kullandığı bütün kodların da saf olmaları gerektiği gerçeğinden yola çıkıldığında saflığın bulaşıcı (viral) olduğu görülür. D'nin saflık kuralları her ne kadar bu konuda kolaylıklar getiriyor olsalar da bazen saf olmayan kodların saf kodlar tarafından çağrılmaları gerekebilir.

Bunun bir örneği, halihazırda yazılmış olan (legacy) kodlardır. Örneğin, saflığın gereklerini yerine getirdiği halde pure olarak işaretlenmemiş olan bir C işlevi bulunabilir. Tür sisteminin kendi başına çıkarsayamadığı başka durumlarda da olduğu gibi, bu durumun üstesinden gelmenin çaresi de cast kullanmaktır. İşlevin adresi alınır, tür dönüşümü ile pure belirteci eklenir, ve işlev o adres yoluyla çağrılır (tür sistemini yanıltabildiğinden @safe kodlar içerisinde bu mümkün değildir). Belirli bir kod içinde böyle numaralara ihtiyacın yeterince fazla olduğu durumlarda safOlduğunuVarsay (assumePure) gibi bir şablondan yararlanılabilir.

Saflık bazen kısa süreliğine de olsa ayak bağı olabilir: Hata ayıklama amacıyla ekrana mesaj yazdırmak için veya istatistiksel amaçla evrensel bir sayacı arttırmak için bir işleve saf olmayan kodlar eklenmiş olabilir. Saf olmayan böyle bir kodun zincirleme çağrılmış olan pure işlevlerin en içtekine eklenmesinin gerekmesi ama bunun tür sistemi tarafından engellenmesi büyük bir sıkıntı kaynağı olurdu. Her ne kadar bazılarınca küçümsenen bir davranış olsa da bu tür kodlar gerçekte oldukça kullanışlıdırlar.

Saflığın hata ayıklama amacıyla geçici olarak etkisizleştirilmesi ihtiyacına rağmen başlangıçta D'de bu kullanıma yönelik hiçbir olanak bulunmuyordu. Sonunda özel bir kural olarak debug blokları içindeki saf olmayan kodların saf kodlar arasında bulunmalarına izin verildi. Bu kullanışlı olanağın etkinleştirilmesi için derleyiciye -debug komut satırı seçeneğinin verilmesi gerekir.

Sonuç

Başlangıçtaki görüşü tekrarlarsak, saflık kavramının önemi, işlevlerin gizli durum kullanmadıklarının tür sistemi tarafından garanti edilmesidir. D'nin pure anahtar sözcüğünün başka dillerdekilerden daha az kısıtlama getirdiğini gördük. Buna rağmen, const ve immutable belirteçleri yardımıyla aynı garantilerin sağlanabildiklerini ve pure'un D'nin başka olanakları ile etkileşimlerinin doğal olabildiğini ve belki de en önemlisi, emirli programlamaya izin verilebildiğini gördük.

Peki, bu konuda daha fazla bilgi için başka kaynaklar nelerdir? Aslında pure anahtar sözcüğü ile ilgili kurallar oldukça kısadır. Bu kurallar için dil referansının dlang.org'daki Functions bölümüne bakabilirsiniz. Don Clugston'ın başlatmış olduğu ve son değişikleri de getirmiş olan tartışma da pure'un gelişimini görmek açısından oldukça ilginçtir. Burada anlatılan kavramların tasarımları ve gerçekleştirmeleri ile ilgili soruları da D dili forumlarında sormak isteyebilirsiniz.

Bu yazıyı beğendiyseniz bana fikrinizi bildirmenizi, yazıyı Twitter'da paylaşmanızı, ve yazının Hacker News'deki ve Reddit'teki tartışmalarına katılmanızı isterim. D hakkında başka yazılarım da var.

Notlar
  1. Bunun etkileri ilk akla geldiğinden çok daha ciddi ve şaşırtıcı olabilir: Bir çok Windows yazıcı sürücüsünün yazdırma işlemi sırasında değiştirdikleri FPU bayraklarını tekrar eski değerlerine çevirmedikleri olurdu. Bir belge yazdırıldıktan sonra bir çok programın çökmesi bunun yüzündendi – bu, yalnızca müşteri bilgisayarında oluşan çökme hatalarının güzel bir örneğidir.
  2. D kodları dilin bazen SafeD olarak adlandırılan bir alt kümesine kısıtlanabilir. Bu olanak üç anahtar sözcük sayesinde her işlev için teker teker ayarlanabilir: @safe olarak işaretlenen kod bellek hataları içeremez; dolayısıyla, bu gibi işlevlerde örneğin gösterge aritmetiği ve C'deki gibi bellek yönetimi kullanılamaz. @system olarak işaretlenmiş olan kod bunun tersidir – bu gibi işlevlerde inline assembly de dahil olmak üzere dilin bütün olanakları kullanılabilir. Son olarak, @trusted belirteci bu iki kavram arasında bir köprü kurar; programcının araştırarak güvenli olduğuna karar verdiği halde aslında güvenli olarak işaretlenmemiş olan kodlara arayüz oluşturur. void* türünden bir C arayüz işlevini sarmalayan bir D işlevi bunun bir örneği olarak görülebilir.
  3. C++'ın tersine, D'de const ve immutable geçişlidir. Bir const referans yoluyla erişilen her değer otomatik olarak const'tır. Örneğin, aşağıdaki kodu ele alalım:
  4. struct Foo {
      int bar;
      int* baz;
    };
    
    void fun(const Foo* foo);
    

    C++'ta fun foo'nun kendisini değiştiremez; örneğin, foo->bar'a yeni bir değer atayamaz. Buna rağmen, foo->baz'ın gösterdiği değeri değiştirmesi yasaldır – buna sığ const (shallow const) da denir. Öte yandan, D'de const derindir; fun içinde foo.baz, const int gösteren const bir göstergedir ve bu yüzden *foo.baz'da da değişiklik yapılamaz. Aynı kural immutable için de geçerlidir; ek olarak, immutable olan veriyi değiştirebilecek başka referans bulunmadığı da garanti edilmiştir. Yani, veriyi fun değiştiremediği gibi, başka hiçbir kod da değiştiremez (immutable değerlerin ROM gibi bir bellek bölgesinde duruyor olduklarını düşünebiliriz). immutable otomatik olarak const'tır da.

  5. Bu örnek yalnızca açıklayıcı olduğu için seçilmiştir; yoksa, bu işlev herhalde ancak çok basit bir görüntüleyicide kullanılabilir. Saflığın bu örneğe bir yarar katıp katmadığı bir yana, bu işlev gerçek bir grafik arayüzü üzerinde gerçekleştirilmiş olsa, GPU'nun durumunun saflığının nasıl sağlanacağı konusu da düşünülmelidir.
  6. C++'ın erişicilerinin (iterator) gösterge genellemeleri olmalarına benzer biçimde, D aralıkları da topluluk kavramının genellemeleridir. En basit aralık üç temel işlev sunar: empty, front, ve popFront. Bu arayüzün kullanıcıları verinin perde arkasında nasıl saklandığından habersizdirler – örneğin, veri bir bellek bölgesinden, ağ üzerinden, standart girişten, vs. geliyor olabilir. Aralıklar algoritmaların kullanabilecekleri basit ama güçlü bir soyutlama sunarlar.