9. Yineleyiciler

Yineleyiciler sadece Ruby'ye özgü bir kavram değildir. Genel olarak çoğu nesneye yönelik yazılım geliştirme dilinde kullanılmaktadır. Lisp'te de yineleyiciler olarak adlandırılmasalar da kullanılmaktadır. Ancak yineleyici kavramı neredeyse her dilde değişik bir anlam kazandığı için önce bu kavramı daha ayrıntılı anlatmaya çalışalım:

Yinelemek sözcüğü aynı şeyi birçok kez tekrarlamak anlamına gelir.

Kod yazarken değişik durumlarda döngülere ihtiyacımız olur. C'de for ya da while kullanarak işimizi hallederiz. Örneğin,

char *str;
for (str = "abcdefg"; *str != '\0'; str++) {
  /* her karakter için işlemler burada */
}

C'nin for(...) sözdizimi döngünün oluşturulmasında soyutlama sağlayarak yardımcı olsa da *str'nin boş bir karakterle sınanması yazılımcının dizge yapısı hakkında daha ayrıntılı bilgi edinmesini gerektirir. Bu C'ye düşük-seviyeli hissi veren nedenlerden biridir. Yüksek seviyeli diller yineleyicilere uyumluluklarıyla ün kazanmışlardır. Aşağıdaki sh kabuk betiğini göz önünde alalım:

#!/bin/sh

for i in *.[ch]; do
  # ... her dosya icin yapilacak birkaç işlem
done

Bulunulan dizindeki tüm C kaynak ve başlık dosyaları çalıştırıldı ve komut satırı ayrıntıları tuttu. C'den daha yüksek seviyeli olduğunu düşünüyorum, öyle değil mi?

Ancak göz önüne alınması gereken başka bir nokta daha var: bir dilin gömülü veri yapıları için yineleyicileri desteklemesi güzel birşey olsa da, geri dönüp kendi veri yapılarımızı tekrarlatacak düşük seviyeli döngüler yazmak hayal kırıklığı yaratacaktır. Nesneye yönelik yazılım geliştirmede, kullanıcılar çoğu kez ardı ardına veri türleri tanımlar ve bu ciddi bir sorun olabilir.

Her nesneye yönelik yazılım geliştirme dili yineleyiciler için kolaylıklar içerir. Bazı diller bu iş için özel sınıflar tanımlarken, Ruby yineleyicileri doğrudan tanımlamayı tercih eder.

Ruby'nin String türü bazı yararlı yineleyicilere sahiptir.

ruby> "abc".each_byte{|c| printf "<%c>", c}; print "\n"
<a><b><c>
   nil

each_byte, dizgedeki her karakter için bir yineleyicidir. Her bir karakter yerel bir değişken olan c'ye yerleştirilir. Bu daha çok C koduna benzeyen bir şeyle açıklanabilir...

ruby> s="abc";i=0
   0
ruby> while i<s.length
    |    printf "<%c>", s[i]; i+=1
    | end; print "\n"
<a><b><c>
   nil

Buna rağmen each_byte yineleyicisi hem kabul edilebilir bir basitliktedir hem de String sınıfı radikal bir değişikliğe uğrasa da çalışmaya devam eder. Yineleyicilerin başka bir yararı da değişiklere karşı sağlam durmasıdır ki bu da iyi bir kodun karakteristik özelliklerinden biridir (evet, sabırlı olun, sınıflar hakkında da konuşacağız.).

String'in başka bir yineleyicisi de each_line'dır.

ruby> "a\nb\nc\n".each_line{|l| print l}
a
b
c
   nil

C'de satır sınırlayıcıları bulmak, alt dizgeler üretmek gibi güç işlerin yineleyicilerle kolayca üstesinden gelinebilir.

Geçen bölümdeki for döngüsü, each işlecini kullanarak tekrarlamayı sağlamaktaydı. String'in each'i aynı each_line gibi görev görür, o yüzden yukarıdaki örneği for ile tekrar yazalım:

ruby> for l in "a\nb\nc\n"
    |   print l 
    | end
a
b
c
   nil

Bulunan yineleyiciyi döngünün başından itibaren tekrar ettirmek için retry denetim yapısını kullanabiliriz.

ruby>  c=0
   0
ruby> for i in 0..4
    |   print i
    |   if i == 2 and c == 0
    |     c = 1
    |     print "\n"
    |     retry
    |   end
    | end; print "\n"
012
01234
   nil

Yineleyici tanımlamasında bazen yield'le karşılaşırız. yield, denetimi yineleyiciye bağımsız değişken olarak geçilen kod bloğuna verir (bu konu Yordam Nesneleri bölümünde daha ayrıntılı anlatılacaktır).

Aşağıdaki örnekte, bağımsız değişkende verilen sayı kadar bir kod bloğunu tekrarlayan repeat yineleyicisi tanımlanmıştır.

ruby> def repeat(num)
    |   while num > 0
    |     yield
    |     num -= 1
    |   end
    | end
   nil
ruby> repeat(3) { print "foo\n" }
foo
foo
foo
   nil

retry ile while gibi çalışan ancak hız açısından pek de pratik olmayan bir yineleyici tanımlayabiliriz.

ruby> def WHILE(cond)
    |   return if not cond
    |   yield
    |   retry
    | end
   nil
ruby> i=0; WHILE(i<3) { print i; i+=1 }
012  nil

Yineleyicinin ne olduğunu anladınız mı? Bir kaç kısıtlama hariç, kendi yineleyicinızı yazabilirsiniz, aslında yeni bir veri türü tanımladığınız zaman ona uygun bir yineleyici tanımlamanız da uygun olacaktır. Yukarıdaki örnekler pek de kullanışlı örnekler sayılmazlar. Sınıfları daha iyi anladığımızda daha uygulanabilir yineleyiciler hakkında konuşabiliriz.