Ders 04 / 12
Denetim Akışı
case'in her when dalına === iletisi göndermesi, if ve while'ın ise ileti göndermeyen sözdizimi olması; deyim değiştiriciler ve if'in değer üretmesi; case'in yanıtlayanının dalın nesnesinin sınıfı olması.
Yayın Güncelleme
Bu derste
- case'in her when dalına === iletisi gönderdiğini sahip ölçümüyle gösterir
- if ve while'ın ileti değil sözdizimi olduğunu defined? ile ayırır
- case'in yanıtlayanını dalın nesnesinin sınıfı olarak bulur ve deyim değiştiricilerle if'in değer ürettiğini gösterir
İçindekiler
Önceki ders atamanın bir ileti değil, dilin kendi yazımı olduğunu ölçtü ve bir soru bıraktı: dilin kendi yazımıyla çalışan if, while ve case’ten hangisi gerçekten ileti gönderiyor? Koşul ve döngü kavramları Programlama Temelleri kursunda (M01/K02) tanıtılmıştı; burada o kavramlar tekrarlanmaz. Bu ders yalnız bir soruyu ölçer: bir case dalının seçilmesi sırasında hangi nesneye hangi ileti gidiyor?
Rust kursunda (M10/K01) “bu yapı değer mi üretiyor” sorusu ölçülmüştü. Ruby’de de neredeyse her denetim yapısı değer üretir, ve o soru bu derste yalnız bir yan gözlem olarak geri gelir. Asıl ölçü, dallanmanın arkasındaki iletidir.
case Her Dala === Gönderir
Aşağıdaki dosya bir aralık nesnesine === iletisi gönderir ve bu iletiyi kimin yanıtladığına bakar. Sonra aynı aralığı bir case içinde when dalına koyar.
# aralik_eslesmesi.rb — case her when dalina === iletisi gonderir
aralik = 1..10
puts aralik.class
puts aralik.method(:===).owner
puts aralik === 7
sonuc = case 7
when 1..10 then "aralikta"
else "disinda"
end
puts sonuc
Range Range true aralikta
1..10 bir Range nesnesidir ve === iletisini Range yanıtlar. Aralığa 7 sorulduğunda cevap true döner. case 7 when 1..10 yazıldığında dal da eşleşir: çünkü case değerini when dalının nesnesine === iletisinin argümanı yapar. Yani when 1..10, perde arkasında (1..10) === 7 sorusudur.
DT10. Bu derste “dal nesnesi”, when satırında yazılan nesnedir; case değeri ise case anahtar sözcüğünden sonra gelen nesnedir. === iletisinin alıcısı dal nesnesi, argümanı case değeridir. Sıra önemlidir: 7 === (1..10) yazılsaydı alıcı 7 olurdu ve yanıtlayan değişirdi.
if ve while İleti Göndermez
case dallarında === iletisi gittiğini gördük. if ve while için aynı soru sorulabilir: bir koşul bir iletiyle mi seçiliyor? defined? buna doğrudan yanıt verir.
# kosul_ifadeleri.rb — if ve while sozdizimidir puts defined?(if true then 1 end) puts defined?(while false do end) puts defined?(1 + 2) puts defined?(case 1 when 1 then 2 end)
expression expression method expression
if ve while "expression" olarak bildirildi; yani bir yöntem çağrısı değil, dilin yapısıdır. 1 + 2 ise "method". case de "expression" çıktı: dalın kendisi sözdizimidir, ama içindeki her when karşılaştırması bir iletiyi gönderir. Aynı yapıda iki katman var: case iskeleti sözdizimidir, dal seçimi ise nesnenin sınıfının yanıtladığı bir iletidir.
Deyim Değiştiriciler ve Değer Üreten if
Ruby’de bir koşulu ya da döngüyü cümlenin sonuna koymak mümkündür. Bunlara deyim değiştirici denir: puts x if y, x += 1 until z. Ayrıca if bir değer üretir; hiçbir dal çalışmazsa bu değer nil olur.
# deyim_degistirici.rb — deyim degistiriciler ve if'in degeri x = 3 puts "buyuk" if x > 2 x += 1 until x >= 6 puts x y = if false then 1 end p y z = if true then 1 end p z
buyuk 6 nil 1
İlk satır x > 2 doğru olduğu için çalıştı. until değiştiricisi x altıya ulaşana kadar toplamayı tekrarladı. if false then 1 end hiçbir dalı çalıştırmadığı için nil döndü, if true then 1 end ise 1 döndü. Sonuç: if bir deyim değil, değer üreten bir ifadedir. Bu, atamanın da 7 değerini taşımasıyla aynı özelliktir.
Doğruluk ve Döngünün Değeri
Koşulun doğru ya da yanlış sayılması da dilin kendi kuralıdır. Ruby’de yalnız iki değer yanlış sayılır: nil ve false. Geri kalan her şey, 0 ve boş dizgi dahil, doğru sayılır. Aşağıdaki dosya beş değeri aynı koşulda dener.
# dogruluk.rb — yalniz nil ve false yanlis sayilir
[nil, false, 0, "", []].each do |deger|
durum = if deger then "dogru" else "yanlis" end
printf("%-5s %s\n", deger.inspect, durum)
end
nil yanlis false yanlis 0 dogru "" dogru [] dogru
nil ve false yanlış, 0, "" ve [] doğru çıktı. Bu kural dilin kendi kuralıdır: yalnız nil ve false yanlış sayılır, geri kalan her değer doğrudur. Döngüler de değer üretir. while döngüsü break ile bir değerle çıkarsa o değer döngünün sonucu olur; break olmadan biterse sonuç nil’dir.
# dongu_degeri.rb — while da deger uretir n = 0 sonuc = while true n += 1 break n * 10 if n == 3 end p sonuc bos = while false do end p bos
30 nil
Döngü üçüncü turda 30 ile çıktı ve sonuc bu değeri taşıdı. Hiç çalışmayan döngü ise nil verdi. Bu, if‘in hiçbir dal çalışmadığında nil üretmesiyle aynı davranıştır: Ruby’de denetim yapıları ifadedir ve değeri vardır.
Döngüler ve Değer Üretmeleri
Önceki bölümler dalları ölçtü; bu bölüm döngüleri ölçer. Soru yine değerle ilgilidir: bir döngü ifadesi bir değer üretir mi, ve break o değeri belirleyebilir mi? Yanıtlayan ekseni bu bölümde iki yerde görünür: while ve until anahtar sözcüktür ve sözdizimidir, loop ise bir yöntemdir ve sahibi çıktıda okunur. İkinci blok ise bir kapsam farkına bakar: for ile blok değişkeni dışarıya aynı biçimde mi davranır?
# donguler.rb — dongulerin degeri ve next
sonuc = loop { break 5 }
puts sonuc
i = 0
i += 1 while i < 3
puts i
j = 10
j -= 1 until j < 8
puts j
bos = while false do end
puts bos.inspect
toplam = 0
(1..5).each do |n|
next if n.even?
toplam += n
end
puts toplam
puts method(:loop).owner
5 3 7 nil 9 Kernel
loop { break 5 } döngünün değeri olarak 5 verdi: break argümanı döngü ifadesinin sonucu olur. i += 1 while i < 3 değiştiricisi i’yi 3’e çıkardı, j -= 1 until j < 8 ise j’yi 7’de durdurdu; while koşul doğru kaldıkça, until koşul yanlış kaldıkça sürer. while false do end ifadesi hiç çalışmadığı hâlde bir değer üretti ve bu değer nil’dir. Yani döngü de, if gibi, bir ifadedir ve değer taşır. (1..5).each içinde next if n.even? çift sayıları atladı; kalan 1, 3, 5 toplandı ve 9 çıktı. next yinelemeyi atlar, break döngüyü bitirir; ikisi de blok içinde yazılır ve ikisi de dilin anahtar sözcüğüdür; bu dosya onlar için bir sahip basmadı. Son satır loop’un sahibinin Kernel olduğunu gösterdi: loop bir anahtar sözcük değil, puts gibi bir yöntemdir.
# for_sizintisi.rb — for degiskeni disari sizar, blok degiskeni sizmaz
[1, 2].each { |k| k }
d = defined?(k)
puts d.inspect
for m in [1, 2] do end
puts defined?(m)
puts m
puts [1, 2].method(:each).owner
puts 1.upto(3).to_a.inspect
puts 3.times.class
nil local-variable 2 Array [1, 2, 3] Enumerator
each bloğunun değişkeni k dışarıdan sorulduğunda nil döndü: blok değişkeni bloğun içinde kalır. for m in [1, 2] do end ise m’yi dışarıya sızdırdı: defined?(m) "local-variable" bildirdi ve m sonunda 2 oldu. Yani for döngüsü yeni bir kapsam açmaz. Aynı işi gören iki yazımın kapsam davranışı farklıdır; bu fark, hangisinin seçildiğini bir tercih olmaktan çıkarıp bir sonuca bağlar. [1, 2].method(:each).owner Array verdi; yani each’i dizinin sınıfı karşılar. 1.upto(3).to_a [1, 2, 3] verdi ve bloksuz çağrılan 3.times bir Enumerator döndürdü: bloksuz çağrılan times değer üretmek yerine bir yineleyici verir.
DT11. for’un arka planda dizinin each iletisini çağırdığı ölçülmedi. Ölçülen yalnızca kapsam farkı ve each’in sahibinin Array olduğudur.
Kısa Devre ve Karşılaştırma
Koşullar yalnız if ve case ile kurulmaz: || ve && işleçleri de sağ tarafı çalıştırıp çalıştırmamaya karar verir. Bu bölüm iki soruyu ölçer: bir işleç sağ tarafı değerlendirmeyi atladığında ne gözlenir, ve karşılaştırma yapan yöntemlerin sahibi kim? Eksen yine yanıtlayandır; || ve && dilin sözdizimidir, <=> ve between? ise iletidir.
# kisa_devre.rb — sag taraf calisir mi, kim karsilar
def iz(ad)
puts ad
true
end
iz("a") || iz("b")
iz("c") && iz("d")
v = nil || "varsayilan"
puts v
w = false && 1
puts w.inspect
puts 1 <=> 2
puts 5.between?(1, 10)
n = 5
durum = n > 3 ? "buyuk" : "kucuk"
puts durum
puts 5.method(:<=>).owner
puts 5.method(:between?).owner
a c d varsayilan false -1 true buyuk Integer Comparable
iz("a") || iz("b") satırında yalnız a basıldı: sol taraf doğru olduğu için sağ taraf hiç çalışmadı. iz("c") && iz("d") satırında ise c ve d ikisi de basıldı, çünkü && sol taraf doğru olunca sağ tarafa da bakar. Kısa devre budur: sonuç sol tarafla belirlenmişse sağ taraf değerlendirilmez. nil || "varsayilan" ifadesi varsayilan verdi; || yalnız doğruluk değeri üretmez, işleneninin kendisini döndürür. false && 1 ise false döndürdü. || ile varsayılan değer vermek bu davranışa dayanır: sol taraf nil ya da false ise sağ taraftaki değer döner.
1 <=> 2 -1 verdi: karşılaştırma iletisi sıralamayı bir tam sayı olarak bildirir. 5.between?(1, 10) true oldu. Üçlü işleç n > 3 ? "buyuk" : "kucuk" buyuk verdi. Sahip satırları iki ayrı yanıtlayanı ortaya çıkardı: <=> iletisini Integer karşılar, between? iletisini ise Comparable modülü karşılar. İkisi de bir sayıya gönderilir ve sonucu verir, ama tanımlandıkları yer farklıdır. Yani koşul yazarken kullanılan karşılaştırma iletileri, sınıfın kendi yöntemleri ile ortak bir modülün yöntemleri arasında bölünmüştür.
Hata Akışı: rescue, else ve ensure
Denetim akışı yalnız koşul ve döngüden ibaret değildir; hata da akışı değiştirir. Bu bölüm rescue, else ve ensure dallarının hangi sırayla çalıştığını ve yöntemin değerini hangisinin belirlediğini ölçer. Hata sınıfı basılır, iletisi basılmaz. Eksen sözdizimidir: rescue ve ensure anahtar sözcüktür, raise ise bir yöntemdir.
# istisna_akisi.rb — rescue, else ve ensure sirasi
def dene
yield
rescue ZeroDivisionError => e
puts e.class
:kurtarildi
else
:sorunsuz
ensure
puts "temizlik"
end
a = dene { 1 / 0 }
puts a.inspect
b = dene { 1 / 1 }
puts b.inspect
c = begin
Integer("x")
rescue ArgumentError
-1
end
puts c
puts method(:raise).owner
puts method(:dene).owner
ZeroDivisionError temizlik :kurtarildi temizlik :sorunsuz -1 Kernel Object
İlk çağrıda blok 1 / 0 hesapladı ve ZeroDivisionError fırlattı. rescue dalı çalıştı, sınıfı bastı ve :kurtarildi döndürdü; ardından ensure dalı temizlik yazdı. Dönüş değeri :kurtarildi olarak kaldı: ensure yöntemin değerini belirlemez. İkinci çağrıda hata çıkmadı, bu yüzden else dalı çalıştı ve yöntemin değeri :sorunsuz oldu; ensure yine temizlik yazdı. Yani ensure her iki yolda da çalışır, rescue yalnız hata olduğunda, else yalnız hata olmadığında çalışır.
begin bloğu da bir ifadedir ve değer taşır. Integer("x") ArgumentError fırlattı, rescue dalı -1 döndürdü ve c bu değeri aldı. Bu, if’in değer üretmesinin hata akışındaki karşılığıdır: dallardan biri çalışır ve çalışan dalın son ifadesi bütünün değeri olur. Sahip satırları raise için Kernel, dene için Object gösterdi: hata fırlatmak bir yöntem çağrısıdır, ama rescue ile ensure dilin kendi yazımıdır. Hata iletisinin metni bu bölümde basılmadı; çünkü iletinin metni sürüme göre değişebilir, sınıfı ise değişmez.
Sınırlayıcı Ölçüm
Bu dersin yanıtlayanı sınıftır. Burada kayma, case’in yanıtlayanını case’e verilen değerin sınıfı sanmaktır. Oysa case 7 ifadesinde 7’nin sınıfı Integer’dır, ama === iletisinin alıcısı dal nesnesidir. Bu yüzden aynı 7 için when Integer satırında Module, when /a/ satırında Regexp, when 1..10 satırında Range yanıtlar.
# dal_nesnesi.rb — case'in yanitlayani dalin nesnesinin sinifidir
dallar = [Integer, /a/, 1..10]
dallar.each do |dal|
printf("%-8s sinifi %-8s ileti sahibi %s\n", dal.inspect, dal.class, dal.method(:===).owner)
end
puts Integer === 7
puts(/a/ === "kalem")
puts dallar[2] === 7
Integer sinifi Class ileti sahibi Module /a/ sinifi Regexp ileti sahibi Regexp 1..10 sinifi Range ileti sahibi Range true true true
Üç dal nesnesi, üç ayrı sahip: Integer için === iletisini Module yanıtlar (önceki dersteki Integer.=== gözlemi), düzenli ifade için Regexp, aralık için Range. case’in değeri hep 7 ya da aynı "kalem" olsa bile değişen yalnız alıcıdır. Sınıf yanıtlayanı olarak okunacak olan, case değerinin değil, dal nesnesinin sınıfıdır.
Bu derste ölçülen iki katmanı birlikte okumak gerekir. Denetim yapısının iskeleti, yani if, while ve case sözcükleri, dilin kendi yazımıdır ve defined? onları "expression" olarak bildirir. Dalın içindeki karşılaştırma ise bir iletidir ve nesnenin sınıfı onu yanıtlar. İkisini ayırmak, bir case ifadesini okurken “bu dal neden eşleşti” sorusunu “dal nesnesinin === yöntemi ne yapıyor” sorusuna çevirir.
Özet
caseherwhendalında dal nesnesine===iletisi gönderir;when 1..10perde arkasında(1..10) === 7sorusudur.===iletisinin alıcısı dal nesnesi, argümanıcasedeğeridir; sıra tersine çevrilirse yanıtlayan değişir.ifvewhiledefined?ile"expression"bildirilir; dilin kendi yapısıdır ve ileti göndermez.- Deyim değiştiriciler (
if,unless,while,until) bir cümleyi koşullu ya da döngülü yapar;ifbir değer üretir ve hiçbir dal çalışmazsanildöner. - Bu dersin yanıtlayanı sınıftır:
case’in yanıtlayanı,casedeğerinin değil dal nesnesinin sınıfıdır. loop { break 5 }5döndürür,while false do endnilverir;fordeğişkeni dışarı sızar, blok değişkeni sızmaz;||ve&&sağ tarafı gerekmedikçe çalıştırmaz,ensureher yolda çalışır.
Sonraki Adım
Koşul ve dal seçimini kimin yanıtladığını ölçtük. Peki bir yöntem çağrısında alıcı hiç yazılmadığında, örneğin puts ya da format yazıldığında, iletiyi kim karşılıyor? Sonraki ders, Yöntemler, alıcısız çağrının yanıtlayanını ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.