Tembel Değerlendirmeler
Tembel değerlendirmeler, işlemlerin gerçekten gerekli olduğu zamana kadar geciktirilmesi anlamına gelir. İngilizcesi "lazy evaluation" olan tembel değerlendirmeler, Haskell gibi bazı programlama dillerinin de temel olanakları arasındadır.
İşlemlerin gerekene kadar geciktirilmeleri, doğal olarak hız kazancı sağlayabilir; çünkü belki de gerekmeyecek olan bir işlem için baştan zaman harcanmamış olur. Öte yandan; bir önceki derste de gördüğümüz gibi, lazy parametrelerin her erişildiklerinde tekrar hesaplanıyor olmaları zaman kaybına da neden olabilir. Bu olanak, dikkatli kullanıldığında ilginç programlama yöntemlerine olanak verir.
D'de tembel değerlendirme olanağı yalnızca lazy belirtecine bağlı değildir; onu aslında daha önce gördüğümüz üç işleçten tanıyorsunuz:
||işleci (veya işleci): İkinci ifadeyi ancak birincisifalseolduğunda işletir
if (birİfade() || belkiDeİşletilmeyecekİfade()) { // ... }
Eğer birİfade()'nin sonucu true ise, sonucun da true olacağı daha ikinci ifade işletilmeden bellidir. O durumda ikinci ifade işletilmez.
&& işleci (ve işleci): İkinci ifadeyi ancak birincisi true olduğunda işletirif (birİfade() && belkiDeİşletilmeyecekİfade()) { // ... }
Eğer birİfade()'nin sonucu false ise, sonucun da false olacağı daha ikinci ifade işletilmeden bellidir. O durumda ikinci ifade işletilmez.
?: işleci (üçlü işleç): koşul true olduğunda önceki ifade, false olduğunda sonraki ifade işletilir int i = birKoşul() ? yaBuİfade() : yaDaBuİfade();
birKoşul()'un sonucuna göre ifadelerden birisi gerekmediği için hiç işletilmez.
lazy anahtar sözcüğü, bu üç işlecin dışında da tembel değerlendirmeler kullanmayı sağlar.
Yararlarından birisi, yukarıda da gördüğümüz gibi işlemlerin gereksizce işletilmelerinin önüne geçmesidir. Bu, program hızı açısından önemli olabilir.
Diğer ve belki de daha önemli yararı, işlemlerin bazı adımlarının sabit olmak yerine, işleve dışarıdan verilebilmesidir. Bu sayede, işlevin davranışı değiştirilebilmektedir. Bunun bir örneğini görmek için önce lazy kullanmayan bir programa bakalım:
import std.cstream; import std.random; void selamVer(in int adet, const char[] selam) { foreach (i; 0 .. adet) { dout.writefln(selam, " dünya!"); } } string rastgeleSelamSeç() { string[] selamlar = [ "Merhaba", "Selam", "Günaydın", "İyi akşamlar" ]; return selamlar[uniform(0,$)]; } void main() { selamVer(5, rastgeleSelamSeç()); }
O programda selamVer işlevinin kullandığı selam parametresinin değeri hevesli olarak hesaplandığı için, selam ifadesi daha selamVer işlevi çağrılmadan önce main içinde belirlenir ve bu yüzden de rastgele seçilen selam beş kere tekrarlanmış olur. Örnek:
İyi akşamlar dünya! İyi akşamlar dünya! İyi akşamlar dünya! İyi akşamlar dünya! İyi akşamlar dünya!
Not: Mesaj programın her çalıştırılışında rastgele seçildiği için, beş kere tekrarlanan mesajın programın her çalıştırılışında farklı olması normaldir.
Oysa, parametrenin tanımında yapılan küçük bir değişiklik, selamın her yazdırılışında rastgele seçilmesini sağlar:
void selamVer(in int adet, lazy const char[] selam) {
Merhaba dünya! Selam dünya! Merhaba dünya! İyi akşamlar dünya! Selam dünya!
Bunun nedeni, foreach döngüsü içinde kullanılan selam değerinin tembel bir şekilde her seferinde gerektikçe hesaplanmasıdır.
Bu, programlama açısından son derece güçlü bir olanaktır. lazy kullanılmadığında, işleve "al bu selamı kullan" demiş gibi oluruz. lazy kullandığımızda ise, "selam gerektiği zaman şu işlevi çağır ve onun döndürdüğü dizgiyi kullan" demiş gibi oluruz.
lazy bildirimi bir anlamda işlevin davranışını değiştirmektedir. Bu örnekte sonunda " dünya!" olan beş mesaj yazdırılacağı kesindir; ama mesajın baş tarafına ne yazdırılacağının seçimi, işleve parametre olarak verilmektedir.
Buradaki fark önemli: işleve bir değer değil, o değerin nasıl hesaplanacağı verilmektedir. Yani parametresi bir değer değildir, bir hesaplama işlemidir.
Örnek
İşlevin davranışını değiştirmenin bir örneği olarak bilgisayar ve kullanıcının zar attıkları bir oyun düşündüm. Attığı iki zarın toplamı daha büyük olan taraf puan kazanıyor.
Programın başında, bilgisayarın hile yapıp yapmayacağı soruluyor. O koşula bağlı olarak; bilgisayar ya hileli zarlar ya da rastgele zarlar atıyor:
hileYapsın ? hileliZarlar() : rastgeleZarlar(),
Yukarıdaki iki işlevden duruma göre birisi zarOyunu işlevine lazy parametre olarak gönderiliyor ve bütün oyun, parametre olarak verilen bu davranışa göre oynatılıyor.
Not: Umarım ?: işlecinin kullanılması karışıklığa neden olmaz. O işleç en başta hangi işlevin kullanılacağına karar vermek için kullanılıyor. Seçilen işlevi kendisi işletmiyor; çünkü seçtiği işlev zarOyunu işlevine bir lazy parametre olarak gönderiliyor.
Diğer açıklamaları programın içine yazdım:
import std.cstream; import std.random; /* * 1 ve 6 arasında eşit dağılımlı bir değer döndürür */ int rastgeleZar() { return uniform(1, 7); } /* * 5 ve 6 arasında eşit dağılımlı bir değer döndürür */ int hileliZar() { return uniform(5, 7); } /* * İki tane rastgele zarın toplamını döndürür */ int rastgeleZarlar() { return rastgeleZar() + rastgeleZar(); } /* * Birisi hileli olan iki zarın toplamını döndürür */ int hileliZarlar() { return rastgeleZar() + hileliZar(); } void bilgiVer(in int oyunSayısı, in int bilgisayarınAtışı, in int oyuncununAtışı, in int bilgisayarınPuanı, in int oyuncununPuanı) { dout.writefln( "%3d: bilgisayar %2d %2d oyuncu skor:%3d - %d", oyunSayısı, bilgisayarınAtışı, oyuncununAtışı, bilgisayarınPuanı, oyuncununPuanı); } void zarOyunu(in int adet, lazy int bilgisayarınZarAtışı, lazy int oyuncununZarAtışı) { int bilgisayarınPuanı; int oyuncununPuanı; foreach (i; 0 .. adet) { /* * lazy parametrelerin değerlerinin yalnızca bir kere * hesaplanmaları için onlara bu döngü içinde bir kere * erişmemiz gerekir. Eriştiğimiz değerleri iki yerel * değişkende saklıyoruz. */ const int bilgisayarınBuAtışı = bilgisayarınZarAtışı; const int oyuncununBuAtışı = oyuncununZarAtışı; if (bilgisayarınBuAtışı > oyuncununBuAtışı) { ++bilgisayarınPuanı; } else if (bilgisayarınBuAtışı < oyuncununBuAtışı) { ++oyuncununPuanı; } bilgiVer(i + 1, bilgisayarınBuAtışı, oyuncununBuAtışı, bilgisayarınPuanı, oyuncununPuanı); } } bool bilgisayarHileYapsınMı() { dout.writef("Bilgisayar hile yapsın mı? (E/H) "); char yanıt; din.readf(&yanıt); return (yanıt == 'e') || (yanıt == 'E'); } void main() { const bool hileYapsın = bilgisayarHileYapsınMı(); zarOyunu(10, hileYapsın ? hileliZarlar() : rastgeleZarlar(), rastgeleZarlar()); }
İşlevlere değer göndermenin yanında davranış da gönderilebildiği kavramını şimdiden anlarsanız, D'nin daha sonra göreceğimiz class, function, ve delegate gibi fonksiyonel programlama ve nesneye yönelik programlama olanaklarını da daha kolay anlayacaksınız.
Problemler
- Bir int dizisi ve bir değer alan bir işlev yazın. Dizinin tek sayılı indekslerindeki (1, 3, 5, vs.) elemanlarının değerleri o değerle değiştirilsin. Örneğin
[0,11,22,33,44,55]gibi bir dizi ve7gibi bir değer verildiğinde dizi şu hale gelsin:[0,7,22,7,44,7]. Bu işlevilazyparametre kullanmadan yazabilirsiniz. - Aynı işlevin değer belirtmek için kullanılan parametresini
lazyolarak belirleyerek, hem bir önceki çözümde olduğu gibi 7 gibi bir değerle, hem de örneğin 100 ve 200 arasında rastgele bir değerle çağrılabilmesini sağlayın. Bu sefer değer alan parametreyilazyyaparak ona hem sabit bir değer, hem de bir işlev verebilirsiniz.
D.ershane
Forum
Wiki
Projeler
Tanıtım
İletişim
Hakları