Ruby'de blok, bir metot çağrısına eklediğiniz ve metodun istediği zaman çalıştırabildiği kod parçasıdır. Bloğu { ... } ya da do ... end ile yazarsınız; metot içinde yield bloğu çağırır, block_given? blok verilip verilmediğini söyler, &block parametresi ise bloğu bir Proc nesnesi olarak yakalar. İki yazım arasındaki tek gerçek fark önceliktir: do ... end süslü paranteze göre daha zayıf bağlanır.
Bloklar Ruby'nin en karakteristik özelliklerinden biridir; each, map ve select gibi metotların hepsi bloklarla çalışır. Bu yazıda kendi metodunuzda blok kabul etmeyi, bloğa değer gönderip sonucunu kullanmayı ve blokların değişkenlerle ilişkisini anlatıyoruz. Örnekler Ruby 4.0 ile çalıştırıldı. Blok parametrelerinin ayrıntıları (it, _1) için blok parametreleri yazısına, blokların nesne hâli için Proc ve lambda yazısına bakabilirsiniz.
Blok nasıl verilir?

Belgelere göre blok, çağıran kapsamdan metoda gönderilen bir closure'dır ve çağrıda her zaman en sonda yer alır. Blok parametreleri |...| arasına yazılır ve metot parametrelerine benzer kurallarla tanımlanır. Topluluk arasında yaygın alışkanlık, tek satırlık bloklarda süslü parantez, çok satırlılarda do ... end kullanmaktır; ancak bu bir dil kuralı değildir.
[1, 2, 3].each { |x| puts x * 10 }
# çıktı: 10
# çıktı: 20
# çıktı: 30
[1, 2].each do |x|
kare = x * x
puts "#{x} karesi #{kare}"
end
# çıktı: 1 karesi 1
# çıktı: 2 karesi 4yield ile bloğu çağırmak
Kendi metodunuzda bloğu çalıştırmak için yield yazarsınız. yield istediğiniz kadar çağrılabilir; her çağrıda blok yeniden çalışır. yield argüman alırsa bunlar blok parametrelerine gider ve yield ifadesinin değeri, bloğun son ifadesinin değeridir. Bu sayede metot, bloğun hesapladığı sonucu kullanabilir.
def iki_kez
yield
yield
end
iki_kez { puts "merhaba" }
# çıktı: merhaba
# çıktı: merhaba
def kare_al(sayi)
sonuc = yield(sayi)
"blok #{sonuc} döndürdü"
end
p kare_al(4) { |x| x * x } # çıktı: "blok 16 döndürdü"Belgeler, bloğu yalnızca çağıracaksanız ve onu başka bir yere aktarmayacaksanız, açık bir blok parametresi tanımlamak yerine yield kullanmayı önerir.
block_given? ile bloğun varlığını kontrol etmek
Blok olmadan yield çalışırsa Ruby LocalJumpError verir. Kernel#block_given?, o anki bağlamda yield çağrısının bir blok çalıştırıp çalıştıramayacağını söyler. Bloğu isteğe bağlı tutan metotlar bu kontrolle iki farklı davranış sunabilir.
def dene
if block_given?
yield
else
"blok yok"
end
end
p dene # çıktı: "blok yok"
p dene { "merhaba" } # çıktı: "merhaba"
def ver
yield
end
begin
ver
rescue LocalJumpError => e
puts "#{e.class}: #{e.message}" # çıktı: LocalJumpError: no block given (yield)
endÇekirdek kütüphanedeki pek çok metot bloksuz çağrıldığında bir Enumerator döndürür. Kendi metodunuzda bu davranışı block_given? ve to_enum ile nasıl kuracağınızı Enumerator ve Lazy yazısında bulabilirsiniz.
&block: bloğu nesne olarak yakalamak

Parametre listesinin sonuna &ad eklerseniz, metoda verilen blok bir Proc nesnesine çevrilir ve bu adla erişilebilir hâle gelir. Böylece bloğu saklayabilir, daha sonra call ile çalıştırabilir ya da başka bir metoda &ad ile aktarabilirsiniz. Blok verilmezse parametrenin değeri nil olur. Blok parametresi her zaman listenin en sonunda olmalıdır.
def sakla(&blok)
blok
end
b = sakla { |x| x * 10 }
p b.class # çıktı: Proc
p b.call(3) # çıktı: 30
def her_birine(liste, &blok)
liste.each(&blok)
end
her_birine([1, 2]) { |x| puts x }
# çıktı: 1
# çıktı: 2
def yok(&blok) = blok
p yok # çıktı: nilBloğu yalnızca başka bir metoda aktaracaksanız ona ad vermeniz de gerekmez: def her_birine(liste, &) = liste.each(&). Ruby 3.1 NEWS dosyasına göre anonim blok argümanı bu sürümle geldi. Çağrı tarafında & operatörü, bir proc ya da lambda nesnesini yeniden bloğa çevirir.
do/end ile süslü parantez farkı: öncelik
İki yazım aynı bloğu üretir, ama belgelerin vurguladığı gibi do ... end bloğu süslü paranteze göre daha düşük önceliklidir. Parantezsiz iç içe çağrılarda süslü parantezli blok en yakındaki (içteki) metoda, do ... end bloğu ise dıştaki metoda bağlanır. Parantez kullanırsanız blok yine dıştaki metoda gider.
def dis(arg = nil)
block_given? ? "dis bloğu aldı" : "dis şunu aldı: #{arg}"
end
def ic
block_given? ? "ic bloğu aldı" : "ic blok almadı"
end
a = dis ic { 1 }
b = dis ic do 1 end
c = dis(ic) { 1 }
p a # çıktı: "dis şunu aldı: ic bloğu aldı"
p b # çıktı: "dis bloğu aldı"
p c # çıktı: "dis bloğu aldı"Bloklar closure olarak çalışır
Blok, tanımlandığı yerdeki yerel değişkenleri görür ve değiştirebilir. Bloğun içinde ilk kez oluşturulan bir değişken ise blok bitince kaybolur. Dıştaki aynı adlı değişkeni yanlışlıkla değiştirmek istemiyorsanız, belgelerde anlatılan blok-yerel değişken yazımını kullanabilirsiniz: parametre listesinde ; işaretinden sonra yazılan adlar yalnızca blok içinde geçerlidir.
sayac = 0
[1, 2, 3].each { |x| sayac += x }
p sayac # çıktı: 6
yer = "dünya"
[1].each do |x; yer|
yer = "blok"
end
p yer # çıktı: "dünya"
[1].each { |x| yeni = x }
p defined?(yeni) # çıktı: nilBlok içinde next, break ve return
Blok içinde next o turu bitirir ve verdiğiniz değer bloğun sonucu olur. break bloğu çağıran metodun tamamını durdurur; metot çağrısının değeri break ile verdiğiniz değer olur. return ise bloğun içinde bulunduğu metottan çıkar. Bu davranışların proc ve lambda için nasıl değiştiği Proc ve lambda yazısında ayrıntılı anlatılıyor.
r = [1, 2, 3, 4].map do |x|
next 0 if x.even?
x
end
p r # çıktı: [1, 0, 3, 0]
r = [1, 2, 3, 4].each do |x|
break x * 100 if x == 3
end
p r # çıktı: 300
def ilk_buyuk(liste)
liste.each { |x| return x if x > 2 }
nil
end
p ilk_buyuk([1, 5, 3]) # çıktı: 5Pratik kalıp: önce ve sonra çalışan kod
Blokların en yaygın kullanım biçimlerinden biri, bir işin öncesinde ve sonrasında hep aynı adımların çalışmasını sağlamaktır: bir kaynağı açıp kapatmak, süre ölçmek ya da log yazmak gibi. Metot hazırlığı yapar, asıl işi yield ile bloğa bırakır ve temizliği ensure bölümünde yapar. Metot gövdesi örtük bir begin bloğu gibi davrandığı için ayrıca begin yazmanız gerekmez. Blok bir hata fırlatsa bile ensure çalışır, metodun dönüş değeri ise bloğun sonucu olur.
def islem(ad)
puts "#{ad}: başla"
yield
ensure
puts "#{ad}: bitir"
end
p islem("rapor") { 42 }
# çıktı: rapor: başla
# çıktı: rapor: bitir
# çıktı: 42
begin
islem("yedek") { raise "disk dolu" }
rescue => e
puts "yakalandı: #{e.message}"
end
# çıktı: yedek: başla
# çıktı: yedek: bitir
# çıktı: yakalandı: disk doluRuby'nin dosya işlemlerinde gördüğünüz bloklu kullanım da aynı fikre dayanır; ayrıntılar için dosya ve IO işlemleri yazısına bakabilirsiniz.
Bloklarla ilgili araçlar bir bakışta
| Araç | Nerede | Ne işe yarar |
|---|---|---|
| yield | Metot gövdesi | Verilen bloğu çalıştırır, değerini döndürür |
| block_given? | Metot gövdesi | Blok verilmiş mi kontrol eder |
| &blok | Parametre listesi | Bloğu Proc nesnesi olarak yakalar |
| & | Parametre ve çağrı | Bloğu adsız olarak aktarır (3.1+) |
| &nesne | Çağrı | Proc veya to_proc destekleyen nesneyi bloğa çevirir |
| { } | Çağrı | Yüksek öncelikli blok, en yakın metoda bağlanır |
| do end | Çağrı | Düşük öncelikli blok, dıştaki metoda bağlanır |
| |x; y| | Blok parametresi | y blok-yerel değişkendir |
Sık yapılan hatalar

- Bloğu kontrol etmeden yield çağırmak. Blok isteğe bağlıysa
block_given?kullanın, yoksaLocalJumpErroralırsınız. - puts ile do/end bloğu kullanmak. Blok yanlış metoda bağlanır; ya süslü parantez kullanın ya da sonucu önce bir değişkene atayın.
- Blok parametresini listenin ortasına koymak.
&blokher zaman en sonda olmalıdır. - Blok içinde oluşturulan değişkeni dışarıda kullanmaya çalışmak. Değişkeni bloktan önce tanımlayın.
- map içinde break ile değer biriktirmeyi beklemek.
breakmetodun tamamını keser ve yalnızca o değeri döndürür.
Sık Sorulan Sorular
Bir metoda birden fazla blok verilebilir mi?
Hayır, her çağrıya en fazla bir blok verilebilir. Birden fazla kod parçası geçirmeniz gerekiyorsa bunları proc ya da lambda nesneleri olarak normal argüman şeklinde verin.
yield mi &block mı kullanmalıyım?
Bloğu yalnızca çalıştıracaksanız yield belgelerin önerdiği yoldur. Bloğu saklamanız, başka bir metoda aktarmanız ya da nesne olarak incelemeniz gerekiyorsa &block kullanın.
Blok bir nesne midir?
Blok kendi başına bir nesne değildir; &block ile yakalandığında ya da proc ile oluşturulduğunda Proc nesnesine dönüşür. Ayrıntılar için Proc ve lambda farkı yazısına bakın.
Bloklar döngülerin yerine mi geçer?
Ruby'de koleksiyonlar üzerinde gezinmek için genellikle each gibi bloklu metotlar kullanılır. while ve until döngüleriyle karşılaştırma için Ruby döngüleri yazısına bakabilirsiniz.
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 · Blok parametreleri, it ve _1 · Proc ve lambda farkı · Enumerator ve Lazy · Enumerable: map, select, reduce · Ruby döngüleri