İçeriğe geç
academia.sh

Ders 08 / 12

Bloklar

Aynı map iletisinin iki farklı blokla iki farklı sonuç verdiği, yöntemin bloğu yield ile çağırdığı ve bloğun sonucu belirlerken sınıfın dönüş kuralını değiştirmediği ölçülür.

Yayın Güncelleme

Bu derste

  • Bir yöntemin kendisine verilen bloğu yield ile çağırdığını gösterir
  • block_given? ve defined?(yield) ile bloğun varlığını sorgular
  • Aynı map iletisinin iki blokla iki sonuç verdiğini ölçer
  • Blok sonucunun each için dönüş değerini değiştirmediğini ölçer
İçindekiler

Önceki ders, Hash.new { 0 }[:x] ile Hash.new { |_, k| k.to_s }[:x] ifadelerinin iki farklı sonuç verdiğini ve farkı iletiyi alan nesnenin değil, verilen bloğun belirlediğini gösterdi. Bloğun kendisi o derste yalnız yazıldı. Bu ders onu konu yapar: blok (block), süslü parantez ya da do ... end ile yazılan ve bir yönteme iletiyle birlikte verilen kod parçasıdır. Blok başlı başına bir değer değildir; yöntem onu çağırdığında koşar. M10/K03, PHP’de bir kapanışın kaç kez koştuğunu ölçmüştü; burada o soru sorulmuyor, soru yine kimin yanıtladığıdır.

Kursun yanıtlayanları sınıf, modül, blok ve sözdizimidir. Bu dersin yanıtlayanı bloktur, ama sınırlayıcı ölçüm bloğun her sonucu belirlemediğini gösterecek.

Aynı İleti, İki Blok

Aşağıdaki ölçüm aynı dizide aynı map iletisini iki farklı bloğa verir. Dizi değişmez, ileti değişmez; yalnız blok değişir. Çekirdek de aynı sonucu sınıflandırır: yanit.rb iki sonuç farklıysa yanıtlayanı blok sayar.

# yanit.rb — bir iletiyi kimin yanitladigini siniflandiran yardimcilar

# Siniflandirma sirayla sorar: once ifade bir ileti mi (defined?),
# sonra iki farkli blok iki farkli sonuc veriyor mu, sonra yontemin sahibi kim.
def yanitlayan(tanim, sahip: nil, sonuc: nil, bloklu: nil)
  return ["sozdizimi", "defined? #{tanim}"] unless tanim == "method"

  if bloklu
    birinci, ikinci = bloklu.call
    return ["blok", "#{birinci.inspect} / #{ikinci.inspect}"] unless birinci == ikinci
  end

  tur = sahip.instance_of?(Class) ? "sinif" : "modul"
  [tur, "#{sahip}, sonuc #{sonuc.inspect}"]
end
# blok.rb — ayni map iletisi iki blokla
require_relative "yanit"

sayilar = [1, 2, 3]
iki_kat = sayilar.map { |n| n * 2 }
metin = sayilar.map { |n| n.to_s }

tur, kanit = yanitlayan(defined?(sayilar.map), bloklu: -> { [iki_kat, metin] })
puts "#{tur}  #{kanit}"
puts sayilar.inspect
blok  [2, 4, 6] / ["1", "2", "3"]
[1, 2, 3]

İlk satır çekirdeğin eleman donusumu gözlemidir: yanıtlayan blok. İkinci satır dizinin değişmediğini gösterir; map yeni bir dizi kurdu. KB3. Ölçüm dizi üzerindedir; map karma yapıda Enumerable’dan gelir ve aynı blok mantığıyla çalışır, ama bu ders onu ayrıca ölçmez.

Yöntem Bloğu Nasıl Çağırır

Bir yöntem tanımı, çağrıldığında blok verilip verilmediğini sorabilir. block_given? bunu doğrudan söyler; yöntem gövdesinde defined?(yield) aynı bilgiyi verir: bloklu çağrıda bir dizgi, bloksuz çağrıda nil. yield, verilen bloğu çağırır ve argümanlarını bloğun parametrelerine aktarır. Üst düzeyde, bir yöntemin dışında yield yazılmaz; her ölçüm bir yöntemin içindedir.

# yield.rb — yontem blogu yield ile cagirir
def iki_kez
  puts "bloklu=#{block_given?} yield=#{defined?(yield).inspect}"
  return unless block_given?

  yield 1
  yield 2
end

iki_kez
iki_kez { |n| puts "blok #{n}" }
bloklu=false yield=nil
bloklu=true yield="yield"
blok 1
blok 2

Bloksuz çağrıda block_given? false, defined?(yield) nil döndü ve yöntem erken çıktı. Bloklu çağrıda ikisi true ve "yield" oldu; blok 1 ve 2 ile iki kez koştu. KB4. defined?(yield) "method" değil "yield" döndürür; yield bir yöntem çağrısı değil dilin kendi yapısıdır.

Bloksuz çağrılan map ve each hata vermez, bir yineleyici (Enumerator) döndürür. Bloklu çağrıda defined? ise "expression" verir; çekirdek bu yüzden iletiyi bloksuz sorgular, daha önce görüldüğü gibi.

# bloksuz.rb — bloksuz cagri yineleyici dondurur
puts [1, 2, 3].map.class
puts [1, 2, 3].each.class
puts defined?([1, 2, 3].map)
puts defined?([1, 2, 3].map { |n| n })
Enumerator
Enumerator
method
expression

Bloğun Değeri ve Çevresi

yield bir ifadedir ve bloğun son ifadesinin değerini döndürür. Yöntem bu değeri kullanabilir; map tam olarak bunu yapar. Blok ayrıca tanımlandığı yerdeki yerel değişkenleri görür: bu, M01/K02’de anlatılan kapanış (closure) davranışıdır ve burada yeniden tanımlanmaz, yalnız ölçülür.

# deger.rb — yield blogun degerini dondurur, blok cevresini gorur
def uc_kat
  sonuc = yield 3
  "yield dondu: #{sonuc}"
end

carpan = 5
puts uc_kat { |n| n * carpan }
carpan = 7
puts uc_kat { |n| n * carpan }

toplam = 0
[1, 2, 3].each { |n| toplam += n }
puts "toplam=#{toplam}"
yield dondu: 15
yield dondu: 21
toplam=6

İki çağrıda blok aynı yazılmıştı, ama carpan değişkeninin o anki değerini gördü: önce 15, sonra 21. Blok tanımlandığında değeri kopyalamadı; çağrıldığı anda değişkenin o anki değerini okudu. each bloğu içinde toplam dışarıdaki değişkeni artırdı. KB6. Bu ders yalnız bloğun tanımlandığı kapsamdaki değişkenleri gördüğünü ölçer, başka kapsam kurallarını ölçmez.

İki Yazım, Çok Parametre

Süslü parantez tek satırlık bloklarda, do ... end çok satırlı bloklarda tercih edilir; ikisi aynı bloğu kurar. Blok birden çok parametre alabilir ve yield kaç değer verirse parametreler onlara sırayla bağlanır.

# yazim.rb — iki yazim ayni blogu kurar, cok parametre
def cift
  yield 1, 2
end

kisa = cift { |a, b| a + b }
uzun = cift do |a, b|
  a + b
end
puts "kisa=#{kisa} uzun=#{uzun}"

{ ad: "Ayse", yas: 30 }.each do |anahtar, deger|
  puts "#{anahtar}=#{deger}"
end
kisa=3 uzun=3
ad=Ayse
yas=30

İki yazım da 3 verdi. Karma yapının each bloğu anahtarı ve değeri iki ayrı parametre olarak aldı. KB7. İki yazımın yalnız bu çağrılarda aynı sonucu verdiği ölçüldü; parantezsiz çağrıda bloğun hangi yönteme bağlandığı Hangi Yönteme Bağlanır bölümünde ayrıca ölçülür.

Kısa Biçimler

Ruby 3.4’ten beri tek parametreli bir blokta it yazılabilir; _1 ise Ruby 2.7’den beri vardır. İkisi de aynı sonucu verir.

# kisa.rb — tek parametreli blokta it ve _1
puts [1, 2].map { it * 3 }.inspect
puts [1, 2].map { _1 * 3 }.inspect
[3, 6]
[3, 6]

Parametre Açma ve Sıra

Blok birden çok parametre alırsa, yöntemin ilettiği tek bir dizi parametrelere dağıtılır. |a, b| bir çifti açar, |a, (b, c)| iç içe diziyi de açar. each_with_index ise elemanla birlikte sırayı da iletir.

# acma.rb — blok parametrelerinde dizi acma
[[1, 2], [3, 4]].each { |a, b| puts "#{a}+#{b}=#{a + b}" }
%w[x y].each_with_index { |harf, sira| puts "#{sira}:#{harf}" }
[[1, [2, 3]]].each { |a, (b, c)| puts "a=#{a} b=#{b} c=#{c}" }
puts [[1, 2], [3, 4]].map { |cift| cift }.inspect
1+2=3
3+4=7
0:x
1:y
a=1 b=2 c=3
[[1, 2], [3, 4]]

[[1, 2], [3, 4]] üzerinde |a, b| her çifti iki parametreye böldü: 1+2=3 ve 3+4=7. each_with_index elemanı ve sırayı ayrı iki parametre olarak verdi. Parantezli (b, c) iç diziyi açtı. Tek parametreli map { |cift| cift } ise diziyi bölmeden olduğu gibi aldı. KB23. Açma yalnız dizi elemanlarında ve bu dört çağrıda ölçüldü.

Bloğun Dış Yereli

Blok, tanımlandığı yerdeki yerel değişkenleri görür ve değiştirebilir. Bu, yukarıda ölçülen kapanış davranışıdır. Blok parametresi dıştaki bir değişkenle aynı adı taşırsa ne olduğu da aşağıda ölçülür; |x; y| yazımı ise noktalı virgülden sonraki adı bloğa yerel yapar.

# dis_yerel.rb — blok dis degiskeni gorur ve degistirir
toplam = 0
[1, 2, 3].each { |n| toplam += n }
puts toplam
n = 100
[1, 2].each { |n| n }
puts n
y = 1
[5].each { |x; y| y = x }
puts y
6
100
1

toplam bloğun dışında tanımlıydı ve blok her turda onu artırdı: sonuç 6. İkinci örnekte blok parametresi n, dıştaki n ile aynı ada sahipti; blok içindeki n ayrı bir değişken oldu ve dıştaki n 100 olarak kaldı. Üçüncüde |x; y| ile bloğa yerel bir y tanımlandı; blok y = x yazsa da dıştaki y 1 kaldı. Blok dıştaki yereli değiştirebilir; bu değiştirme yalnız ad bloğa yerel olarak bildirilmediyse olur. KB24.

Hangi Yönteme Bağlanır

İki blok yazımı bu dersin başında aynı sonucu verdi. Fark parantezsiz çağrıda ortaya çıkar: süslü parantez en yakın yönteme bağlanır, do ve end ise aşağıdaki ölçümde dıştaki yönteme bağlandı. Aşağıdaki sor yöntemi yalnız kendisine blok verilip verilmediğini bildirir.

# oncelik.rb — blogun hangi yonteme baglandigi
def sor(*) = "blok=#{block_given?}"
puts sor(1) { }
puts sor 1 do end
blok=true
blok=false

sor(1) { } bloğu sor’a verdi ve blok=true basıldı. puts sor 1 do end bloğu puts’a bağladı; sor blok almadı ve blok=false basıldı. Parantezsiz çağrıda do ve end bloğu dıştaki yönteme götürür. KB25. Yalnız bu iki çağrı ölçüldü; zincirleme çağrılardaki bağlanma ayrıca ölçülmedi.

Bloktan Değer Döndürmek

Blok içinde next, bloğu o turda bitirir ve verilen değeri bloğun değeri yapar. break ise yöntemin kendisini bitirir ve verilen değer yöntem çağrısının sonucu olur.

# sonraki.rb — next ve break ile deger
puts [1, 2, 3, 4].map { |n| next 0 if n.even?; n * 10 }.inspect
puts [1, 2, 3].each { |n| break n * 5 if n == 2 }
[10, 0, 30, 0]
10

next 0 if n.even? çift sayılarda bloğun değerini 0 yaptı ve map [10, 0, 30, 0] döndürdü. each içinde break n * 5, n == 2 olduğunda döngüyü kesti ve each çağrısının değeri 10 oldu. KB26. next ve break yalnız map ve each ile ölçüldü.

Esnek Parametre Sayısı

Blok, yöntemin ilettiği değer sayısıyla parametre sayısının eşit olmasını şart koşmaz. Fazla iletilen değerler atılır, eksik kalan parametre nil olur. Tek bir dizi iletildiğinde ise birden çok parametre onu açar.

# esnek.rb — blok parametre sayisi ile iletilen deger sayisi
def iki
  yield 1, 2
end
iki { |a| puts "a=#{a.inspect}" }
iki { |a, b, c| puts "c=#{c.inspect}" }
def tek
  yield [1, 2]
end
tek { |a, b| puts "a=#{a} b=#{b}" }
tek { |a| puts "a=#{a.inspect}" }
a=1
c=nil
a=1 b=2
a=[1, 2]

yield 1, 2 iki değer iletti. Tek parametreli blok yalnız ilkini aldı, üç parametreli blokta üçüncü nil kaldı ve hata çıkmadı. yield [1, 2] tek bir dizi iletti: iki parametreli blok onu açtı, tek parametreli blok diziyi bütün olarak aldı. Bu gevşeklik blokta ve proc’ta görülür; Proc ve Lambda dersi aynı sınamayı lambda ile yapar. KB39.

Bloğun Değerini Yöntemde Kullanmak

yield bir ifadedir ve değeri bloğun son ifadesidir. Yöntem bu değeri biriktirebilir ya da bir koşul olarak kullanabilir; ne yapılacağını bu durumda yöntem değil, verilen blok belirler.

# yield_deger.rb — yield ifadesinin degeri
def donustur(liste)
  sonuc = []
  liste.each { |x| sonuc << yield(x) }
  sonuc
end
puts donustur([1, 2, 3]) { |x| x * 2 }.inspect
def say(liste) = liste.count { |x| yield x }
puts say([1, 2, 3, 4]) { |x| x.even? }
[2, 4, 6]
2

donustur her yield(x) değerini sonuca ekledi ve [2, 4, 6] verdi. say bloğun doğru döndürdüğü elemanları saydı ve 2 verdi. İki yöntemde de sonucu blok belirledi; donustur iki katına almayı, say da çift sayıyı seçmeyi bilmiyor; ikisi yalnız bloğun değerini kullanıyor. Yanıtlayan bu yüzden bloktur. KB40.

Blok Parametresi ve Sembol

Bir yöntem &blok parametresi tanımlarsa, verilen bloğu bir nesne olarak alır; blok verilmediyse bu parametre nil olur. &:ad yazımı ise bir sembolü blok yerine geçirir.

# blok_nesnesi.rb — &blok parametresi ve sembol
def al(&b) = b
puts al.inspect
puts al { }.class
puts %w[a b].map(&:upcase).inspect
puts :upcase.to_proc.call("x")
nil
Proc
["A", "B"]
X

al bloksuz çağrılınca nil, bloklu çağrılınca bir Proc döndürdü. map(&:upcase) her elemana upcase iletisini gönderdi ve ["A", "B"] verdi; :upcase.to_proc.call("x") da X döndürdü. Sembolün kendisi blok değildir, to_proc ile bir Proc’a dönüşür. KB41. Proc nesneleri Proc ve Lambda dersinde ayrıntılanır.

Sınırlayıcı Ölçüm

Blok her zaman sonucu belirler mi? each iletisi bloğu her eleman için koşturur, ama dönüş değeri bloğun sonucu değildir. İki farklı blok aynı dönüş değerini verir ve yanıtlayan blok değil, each’in sahibi olan sınıftır.

# donus.rb — each icin blok sonucu donusu degistirmez
sayilar = [1, 2]
a = sayilar.each { |n| n * 10 }
b = sayilar.each { |n| n * 99 }
puts a.inspect
puts b.inspect
puts "sahip=#{sayilar.method(:each).owner}"
[1, 2]
[1, 2]
sahip=Array

İki blok farklı değerler hesapladı, ama ikisi de each’ten [1, 2] döndü. Dönüş değerini bloklar değil, Array sınıfındaki each tanımı belirliyor. Yani map için blok sonucu belirler, each için belirlemez; aynı blok sözdizimi iki iletide iki farklı iş görür. KB5. Bu fark yalnız bu iki yöntemde ölçüldü, diğer yineleme yöntemlerine genellenmez.

Özet

  • Blok, bir yönteme iletiyle verilen kod parçasıdır; yöntem onu yield ile çağırır.
  • Bu dersin yanıtlayanı bloktur: aynı map iletisi { |n| n * 2 } ile [2, 4, 6], { |n| n.to_s } ile ["1", "2", "3"] verdi.
  • block_given? ve defined?(yield) bloğun varlığını sorar; bloksuz map ve each bir yineleyici döndürür.
  • it Ruby 3.4’ten beri, _1 Ruby 2.7’den beri tek parametreli blokta kullanılır.
  • Sınırlayıcı ölçüm: each iki farklı blokla da [1, 2] döndürür; dönüş değerini bu durumda Array sınıfı belirler.

Sonraki Adım

map dizide Array’in, karma yapıda Enumerable’ın yöntemiydi. Sonraki ders bu modülün kendisine bakar: dizinin kendisinde olmayan bir yöntem nereden geliyor ve kendi sınıfınıza aynı yetenek tek bir each ile nasıl verilir.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat