Ruby'de tekrar için iki yol vardır: while, until, for gibi döngü anahtar kelimeleri ve each, times, upto, loop gibi blok alan metotlar. Günlük Ruby kodunda çoğunlukla ikinci yol kullanılır: bir koleksiyonu dolaşmak için each, belirli sayıda tekrar için times, sonsuz döngü için loop. Döngüden çıkmak için break, sonraki tura geçmek için next, aynı turu yeniden çalıştırmak için redo kullanılır.

Bu yazıda her döngü biçiminin ne zaman çalıştığını, ne döndürdüğünü ve kapsam farklarını Control Expressions, Integer ve Kernel belgeleri temelinde, Ruby 4.0 ile çalıştırılmış örneklerle anlatıyoruz. Serinin diğer konuları için Ruby öğrenme rehberi sayfasına bakın.

Döngü biçimlerinin karşılaştırması

Döngü biçimlerinin karşılaştırması: while koşul, until koşul, for x in liste, liste.each
YapıNe zaman çalışırDönüş değeriYeni kapsam açar mı
while koşulKoşul doğru oldukçanil (break ile değer verilebilir)Hayır
until koşulKoşul yanlış oldukçanil (break ile değer verilebilir)Hayır
for x in listeHer eleman içinDolaşılan nesneHayır
liste.eachHer eleman içinAlıcı nesneEvet (blok)
n.timesn keznEvet (blok)
a.upto(b)a dan b ye kadaraEvet (blok)
loopbreak gelene kadarbreak değeri veya StopIteration sonucuEvet (blok)

while ve until

while, koşul doğru olduğu sürece gövdeyi tekrarlar; koşul döngüye girmeden önce ve her turdan sonra sınanır. until bunun tersidir: koşul yanlış olduğu sürece çalışır. Her ikisinde de do anahtar kelimesi isteğe bağlıdır. Belgelere göre bu döngülerin değeri, break ile bir değer verilmedikçe nil'dir.

ruby
i = 1
while i <= 3
  puts "Tur #{i}"
  i += 1
end
# Çıktı:
# Tur 1
# Tur 2
# Tur 3

geri = 3
until geri.zero?
  puts geri
  geri -= 1
end
# Çıktı:
# 3
# 2
# 1

while ve until da tek satırlık modifier biçimde yazılabilir. Gövdenin koşuldan önce en az bir kez çalışmasını istiyorsanız begin ... end while kullanılır; burada koşul ancak ilk turdan sonra sınanır:

ruby
n = 1
n *= 2 while n < 100
p n          # çıktı: 128

deneme = 10
begin
  puts "en az bir kez çalıştı"   # çıktı: en az bir kez çalıştı
  deneme += 1
end while deneme < 5

for ... in

for döngüsü, verilen nesne üzerinde each ile dolaşır. Belgelerin belirttiği önemli fark, for döngüsünün each bloğu gibi yeni bir kapsam açmamasıdır: döngü değişkeni ve gövdede tanımlanan değişkenler döngü bittikten sonra da erişilebilir kalır. Belgeler ayrıca for döngüsünün modern Ruby programlarında nadiren kullanıldığını not eder.

ruby
for meyve in ["elma", "armut"]
  puts meyve
end
# Çıktı:
# elma
# armut
p meyve         # çıktı: "armut"

["elma", "armut"].each do |m|
  ic = m.upcase
end
p defined?(m)   # çıktı: nil
p defined?(ic)  # çıktı: nil

Bu kapsam farkı, döngü değişkeninin yanlışlıkla dışarıdaki bir değişkeni ezmesine yol açabilir. Blok kapsamının ayrıntıları değişkenler yazısında anlatılıyor.

each ve diğer yineleyici metotlar

each ve diğer yineleyici metotlar

Ruby'de bir koleksiyonu dolaşmanın olağan yolu each metodudur. Dizide her elemanı, hash'te her anahtar-değer çiftini bloğa verir. Sıra numarası da gerekiyorsa each_with_index kullanılır. Bloklar ve yield konusunun ayrıntıları Ruby blok ve yield yazısında.

ruby
fiyatlar = { elma: 30, armut: 45 }
fiyatlar.each do |urun, fiyat|
  puts "#{urun}: #{fiyat} TL"
end
# Çıktı:
# elma: 30 TL
# armut: 45 TL

%w[a b c].each_with_index do |harf, i|
  puts "#{i}=#{harf}"
end
# Çıktı:
# 0=a
# 1=b
# 2=c

times, upto, downto ve step

Belirli sayıda tekrar için Integer sınıfının metotları kullanılır. times bloğu 0'dan n - 1'e kadar sayılarla çağırır. upto yukarı, downto aşağı doğru sayar. Numeric#step ise by: ile adım, to: ile sınır alır. Bu metotların hepsi blok verilmeden çağrıldığında bir Enumerator döndürür; bu nesneler Ruby Enumerator ve lazy yazısında ele alınıyor.

ruby
p 3.times.to_a                  # çıktı: [0, 1, 2]

kareler = []
1.upto(4) { |n| kareler << n * n }
p kareler                       # çıktı: [1, 4, 9, 16]

geri = []
5.downto(3) { |n| geri << n }
p geri                          # çıktı: [5, 4, 3]

adimlar = []
0.step(by: 25, to: 100) { |n| adimlar << n }
p adimlar                       # çıktı: [0, 25, 50, 75, 100]

p 5.upto(4) { |n| raise "çalışmaz" }   # çıktı: 5

Son satır belgelerdeki bir ayrıntıyı gösteriyor: başlangıç sınırdan büyükse upto bloğu hiç çağırmaz ve yalnızca alıcıyı döndürür. Aralıklar da ((1..5).each) aynı işi görebilir; Ruby Range yazısına bakın.

loop: sonsuz döngü

Kernel#loop verilen bloğu durmadan tekrarlar; döngüden break ile çıkılır. break bir değer alırsa loop ifadesinin sonucu o değer olur. Belgelere göre blok içinde StopIteration istisnası yükseltilirse döngü sessizce biter ve loop istisnada saklanan sonucu döndürür; bu, harici bir Enumerator üzerinde next ile ilerlerken döngünün kendiliğinden durmasını sağlar.

ruby
sayi = 0
sonuc = loop do
  sayi += 1
  next if sayi.odd?
  break sayi * 10 if sayi > 5
end
p sonuc            # çıktı: 60

harfler = %w[x y].each
donen = loop do
  puts harfler.next
end
# Çıktı:
# x
# y
p donen            # çıktı: ["x", "y"]

İkinci örnekte harfler.next üçüncü çağrıda StopIteration yükseltir; loop bunu yakalar ve dizinin each metodunun dönüş değerini, yani dizinin kendisini döndürür.

break, next ve redo

break döngüyü veya bloğu hemen bitirir ve isteğe bağlı bir değer döndürür. next o turun geri kalanını atlar; bir değer verilirse bu değer o turun blok sonucu olur, bu da map gibi metotlarda o elemanın yeni değerini belirler. redo ise koşulu yeniden sınamadan aynı turu baştan çalıştırır; kontrolsüz kullanılırsa sonsuz döngüye yol açabileceği için sınır koymak gerekir.

ruby
ilk_cift = [3, 7, 8, 11, 12].each do |n|
  break n if n.even?
end
p ilk_cift   # çıktı: 8

sonuc = [1, 2, 3, 4].map do |n|
  next 0 if n.even?
  n * n
end
p sonuc      # çıktı: [1, 0, 9, 0]

denemeler = 0
[1, 2, 3].each do |n|
  denemeler += 1
  redo if n == 2 && denemeler < 4
  puts "#{n} işlendi, toplam deneme: #{denemeler}"
end
# Çıktı:
# 1 işlendi, toplam deneme: 1
# 2 işlendi, toplam deneme: 4
# 3 işlendi, toplam deneme: 5

Belgelere göre eski Ruby 1.8'de redo yerine retry da kullanılabiliyordu; bugün rescue dışında retry yazmak SyntaxError verir. retry kullanımı Ruby hata yönetimi yazısında anlatılıyor.

İç içe döngülerden çıkmak: catch ve throw

break yalnızca içinde bulunduğu döngüyü veya bloğu bitirir. İç içe birden fazla döngüden tek seferde çıkmak için catch ve throw kullanılabilir. Belgelere göre bu ikili, istisnalardan farklı olarak beklenen akış değişiklikleri için tasarlanmıştır; throw ile verilen ikinci değer catch bloğunun sonucu olur.

ruby
bulunan = catch(:bulundu) do
  [[1, 2], [3, 4], [5, 6]].each do |satir|
    satir.each do |hucre|
      throw :bulundu, hucre if hucre > 3
    end
  end
  nil
end

p bulunan   # çıktı: 4

Döngü değerleri

Ruby'de döngüler de birer ifadedir. Yukarıdaki tablodaki dönüş değerleri pratikte şöyle görünür; özellikle each metodunun alıcıyı döndürmesi, ardından başka bir metot zincirlemenize imkân verir:

ruby
p [1, 2].each { |x| x * 10 }     # çıktı: [1, 2]
p 3.times { |i| i }              # çıktı: 3
r = for k in [7, 8] do end
p r                              # çıktı: [7, 8]
w = while false do end
p w                              # çıktı: nil

Sık yapılan hatalar

Sık yapılan hatalar: Sayacı artırmayı unutmak, for döngüsünün değişkeni sızdırdığını bilmemek, do ... end bloğunu argümansız p veya puts ile kullanmak, break ile tüm iç içe...
  • Sayacı artırmayı unutmak: while ve until koşulu hiç değişmezse döngü bitmez. Ctrl+C ile programı durdurabilirsiniz.
  • for döngüsünün değişkeni sızdırdığını bilmemek: döngü değişkeni döngüden sonra da yaşar ve dışarıdaki aynı adlı değişkenin üzerine yazar.
  • do ... end bloğunu argümansız p veya puts ile kullanmak: blok yanlış metoda bağlanır; parantez veya süslü parantez kullanın.
  • break ile tüm iç içe döngülerden çıkılacağını sanmak: yalnızca en içteki döngü biter; dıştaki döngüler için catch/throw veya bir metot içinde return kullanın.
  • Sınırsız redo: koşulu yeniden sınamadığı için kolayca sonsuz döngü üretir.

Sık Sorulan Sorular

Ruby'de for mu each mi kullanmalıyım?

Belgeler for döngüsünün modern Ruby kodunda nadiren kullanıldığını belirtir. each blok kapsamı sayesinde değişken sızdırmaz ve diğer koleksiyon metotlarıyla aynı yazım biçimini paylaşır; bu yüzden genellikle tercih edilir.

Ruby'de do-while döngüsü var mı?

Ayrı bir do-while anahtar kelimesi yoktur, ancak begin ... end while koşul biçimi gövdeyi koşuldan önce bir kez çalıştırır ve aynı işi görür. Alternatif olarak loop içinde sonda break unless koşul yazabilirsiniz.

break ile return arasındaki fark nedir?

break içinde bulunduğu döngüyü veya bloğu bitirir ve kod döngüden sonraki satırdan devam eder. return ise içinde bulunduğu metodun tamamından çıkar. Bloklarda ve lambdalarda return davranışı farklılaşır; bu konu Ruby proc ve lambda farkı yazısında.

Döngüde sıra numarasına nasıl ulaşırım?

each_with_index bloğa eleman ile birlikte sıfırdan başlayan sırayı verir. Dönüşüm yaparken sıraya ihtiyacınız varsa each_with_index.map veya map.with_index kullanılabilir; bu zincirlerin nasıl çalıştığı Enumerator yazısında açıklanıyor.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Ruby öğrenme rehberi · Ruby if, unless ve case · Ruby blok ve yield · Ruby Enumerable · Ruby Enumerator ve lazy