April 09, 2012

UFCS'i destekleyen derleyici (galiba 2.059 olacak) çıktıktan sonra bu konuyu kitaba da ekleyeceğim.

Bir üye işlev çağrıldığında o işlevin hangi nesne üzerinde çağrıldığını belirtmek için gizli bir parametre daha gönderilir. 'this' adındaki bu parametre C++'ta bir göstergedir, D'de ise bir sınıf değişkenidir. Üye işlevler içinde bazen o parametreyi açıkça kullanarak this.üye yazarız.

Aşağıdaki programda a ve b diye iki nesne var. ikiKatı() işlevi içinde kısaca 'i' yazıldığı halde aslında this.i anlamına gelir ve this o çağrı sırasında hangi nesneyi gösteriyorsa onun i üyesinin değeri kullanılır:

import std.stdio;

class Sınıf
{
   int i;

   this(int i)
   {
       this.i = i;
   }

   int ikiKatı() const
   {
       writeln(typeof(this).stringof); // <- const(Sınıf) yazar
       return i * 2;                   // <- this.i * 2 ile aynı şey
   }
}

void main()
{
   auto a = new Sınıf(1);
   auto b = new Sınıf(5);

   writeln(a.ikiKatı());
   writeln(b.ikiKatı());
}

Çıktısı:

'const(Sınıf)
2
const(Sınıf)
10'

Ek olarak, this isimli bu gizli parametre geleneksel olarak işlevin ilk parametresi olarak geçirilir. Yani sonuçta aslında a.ikiKatı() yazdığımızda derleyici perde arkasında ürettiği kodlarda Sınıf.ikiKatı(a) olarak çağırır. (Tabii o söz dizimi bizim yazdığımız kaynak kodda derleme hatasına yol açar.)

Nesnenin gizli olarak ilk parametre olarak gönderilmesi aslında C'den beri kullanılar bir yöntemdir. Tabii C'de gizli olarak değil, açıkça yazılır. Hatta NYP'ye uygun olarak yazılmış olan C işlevlerinde belirli bir yapı üzerinde işlemek üzere yazılmış olan bir grup işlev ilk parametre olarak hep o yapının bir göstergesini alırlar:

// C islevi
int ikiKati(struct Yapi * nesne)
{
   return nesne->i * 2;
}

Şimdi buna uygun olarak serbest bir D işlevi yazalım. Bu işlev de ilk parametresi olarak Sınıf alsın:

int üçKatı(const Sınıf nesne)
{
   return nesne.i * 3;
}
// ...
   writeln(a.ikiKatı());   // üye işlev çağrısı
   writeln(üçKatı(a));     // serbest işlev çağrısı

Buraya kadar güzel: Üye işlevleri çağırırken nesne.üyeİşlev() diyoruz ve serbest işlevleri çağırırken işlev(nesne) diyoruz.

Bazı programcılar bu ayrımın şart olmadığını senelerdir savunmuşlardır. Ben C++ forumlarındaki tartışmaları senelerce öncesinden hatırlıyorum. Bu programcılara göre, madem üye işlev çağrıldığında nesne gizli de olsa zaten ilk parametre olarak gönderilmektedir, o zaman neden bunun tersi de uygulanmamaktadır? İlk parametre olarak bir nesne kullanmışsak neden öyle bir işlevi de üye işlev gibi çağıramıyoruz?

Yani sonuçta üçKatı() işlevi de keşke şöyle çağrılabilsindir:

   writeln(a.üçKatı());

Sınıf'ın üçKatı() diye bir üye işlevi yoksa, derleyicinin yapması gereken tek şey, üçKatı() isminde ve ilk parametre olarak Sınıf alan bir işleve bakmaktır. Öyle bir işlev varsa bizim a.üçKatı() diye yazdığımızı derleyici üçKatı(a)'ya dönüştürüp o serbest işlevi çağırabilir.

Zararı: Üye işlev gibi çağrıldığı halde aslında serbest işlevdir. Hata ayıklarken veya kodu anlamaya çalışırken güçlük doğurabilir. (Aslında benim bu konuda hiç deneyimim yok çünkü hiç UFCS sunan bir dil kullanmadım.)

Yararları: Yapılara ve sınıflara dışarıdan üye işlev eklenebilir. Hatta bunu modülümüzün kullanıcıları bile kendileri yapabilirler.

İşte bu olanağa UFCS deniyor. Aslında D'de UFCS uzun zamandan beri vardı ama yalnızca dizilere özeldi ve yalnızca bir kaç özel işlevle kullanılabiliyordu. empty(), front(), popFront(), vs. aralık işlevleri std.array modülünde aslında serbest işlev olarak tanımlanmışlardır ama dizi.popFront() diye üye işlev gibi çağrılabilirler. Bu ayrıcalık da bizim türlerimizi ikinci sınıf vatandaş durumuna düşürüyordu.

dmd 2.058 bu kısıtlamayı biraz olsun giderdi: Artık dizilere kendimiz de üye işlev ekleyebiliyoruz:

import std.stdio;

void başınıSonunuKırp(ref string dizgi)
{
   if (dizgi.length) {
       --dizgi.length;

       if (dizgi.length) {
           dizgi = dizgi[1..$];
       }
   }
}

void main()
{
   auto s = "merhaba dünya";
   s.başınıSonunuKırp(); // <- dmd 2.058'in sağladığı ucundan accık UFCS :)
   writeln(s);
}

dmd 2.059 (belki de daha sonraki bir sürüm) bir kısıtlamayı daha da giderecek: UFCS bütün yapı ve sınıf türleriyle kullanılabilecek.

Bu olanağın görünenden çok daha büyük bir yararı var: Aralıklarda çok rastlanan zincirleme çağrılar da çok daha kullanışlı hale gelecekler.

Örneğin bugün şöyle yazmak zorunda kalıyoruz:

   writeln(take(cycle(take(FibonacciSerisi(), 5)), 20));

Okuyalım: FibonacciSerisi'nin ilk 5 elemanının sürekli olarak tekrarlanmışının ilk 20 elemanı.

Görüldüğü gibi bir terslik var: Kodun içeriden dışarıya doğru okunması gerekiyor. Örneğin en baştaki take() ile ilgili olan, en sondaki 20!

Bir de UFCS'in getireceği söz dizimine bakalım:

   FibonacciSerisi().take(5).cycle().take(20).writeln();

Not: writeln()'ın sonda kullılabileceğinden emin değilim. Zaten belki de onun başta olması bu durumda zaten daha uygun.

Böylece D yalnızca olanakları açısından değil, söz dizimi açısından da fonksiyonel dillere yaklaşmış oluyor.

Ali

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]

April 10, 2012

Bu konuda önyargılı olmak istemem ama serbest metotlarında bir üye metot gibi kullanılmalarının getirdiği karışıklık benimda kafamda bazı şüphelere sebep oldu. Diğer taraftan programcıların tersden düşündükleri duruma bende hak veriyorum. neden olmasın.

Ayrıca yine zincirleme yazıma sağladığı kolaylıkta aslında bazen oldukça kullanışlı olduğunu gösteriyor. 2.059 sürümü ile birlikte sanırım üzerinde daha fazla konuşmaya başlayabiliriz.

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]