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 yaratılmasında soyutlama sağlayarak yardımcı olsa da *str'nin bir boş bir karakterle sınanması yazılımcının dizge yapısı hakkında daha ayrıntılı bilgi edinmesini gerektirir. Bu C'nin düşük-seviyeli olduğunu hissettiren nedenlerden biridir. Yüksek seviyeli diller yineleyicilere uyumluluklarıyla ün kazanmışlardır. Aşağıdaki sh kabuk betiğini göz önünde bulunduralı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ığı yaratacak bir iş olacaktır. Nesneye
yönelik yazılım geliştirmede, kullanıcılar çoğu kez ardı ardına veri türleri
tanımlarlar 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 parametre 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, argümanda 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.