Ders 08 / 09
Eksik Yöntem Yakalama
Hiçbir tanımı olmayan bir iletiyi method_missing ile yakalayan vekil nesnenin ölçümü ve bu yakalamanın yansıma sorularında bıraktığı boşluklar.
Yayın Güncelleme
Bu derste
- method_missing ile tanımı olmayan bir iletiyi yakalayan vekil nesne yazar
- Yakalanan yöntemin yansıma sorularında neden görünmediğini ölçer
- respond_to_missing? ile yansımayı yakalama davranışıyla uyumlu hale getirir
- Yakalanan ilk çağrıda yöntem tanımlayan vekilde kimse yazanının kalıcı olmadığını gösterir
İçindekiler
Önceki ders, bir yöntemin define_method ile çalışma zamanında tanımlandığını ve yazanının kod olduğunu ölçtü. Orada her ad, çağrılmadan önce tanımlanmıştı. Bu ders tersini sorar: çağrılan ada hiçbir tanım yoksa Ruby ne yapar? Varsayılan cevap NoMethodError‘dır. Ama bir sınıf bu boşluğu kendisi doldurabilir. Eksik yöntem yakalama (method_missing), bir nesneye gönderilen ve yöntem arama zincirinde hiçbir tanım bulamayan iletinin, hata vermeden önce nesnenin kendi method_missing yöntemine teslim edilmesidir. Ruby’de bu yöntemin adı koddur ve olduğu gibi yazılır.
Eksen yine aynı: bu yöntemi kim yazdı? Bu dersin yazanı kimse. Çağrılan ada bir tanım yoktur; ileti yine de cevap alır, çünkü bir başka yöntem onu yakalar. Yazan yardımcısı için bu, dört kategorinin sonuncusudur ve aşağıda neden burada kullanıldığı görülecek. Önce yardımcı, önceki derslerle aynı biçimde verilir.
# yazan.rb — bir yontemi kimin yazdigini siniflandiran yardimci
# Siniflandirma sirayla sorar: once alicinin zincirinde bir tanim var mi,
# sonra tanimi alicinin kendi sinifi mi tasiyor, sonra tanim nasil uretildi.
def yazan(alici, ad)
zincir = alici.singleton_class
unless zincir.method_defined?(ad) || zincir.private_method_defined?(ad)
return ["kimse", "tanim yok"]
end
tanim = zincir.instance_method(ad)
sahip = tanim.owner
yer = sahip == zincir ? "tekil sinif" : sahip.to_s
return ["ata", yer] unless sahip == alici.class || sahip == zincir
iz = RubyVM::InstructionSequence.of(tanim)
return ["kod", "#{yer}, ruby govdesi yok"] if iz.nil?
return ["kod", "#{yer}, blok govdesi"] if iz.label.start_with?("block")
["programci", "#{yer}, def govdesi"]
end
Bu blok kaynaktır; dosya diske yazılır ve require_relative ile çekilir.
Ölçülen Sınıf
Bu dersin gözlemi bir vekil nesne (proxy) üzerindedir: başka bir nesneyi sarmalayan ve ona gelen iletileri o nesneye ileten nesne. Vekil, sarmaladığı nesnenin yöntemlerinin hiçbirini kendisi tanımlamaz.
# yazanlar.rb — bu dersin gozlemi icin gereken tanim
require_relative "yazan"
class Vekil
def initialize(hedef)
@hedef = hedef
end
def method_missing(ad, *argumanlar, &blok)
return super unless @hedef.respond_to?(ad)
@hedef.public_send(ad, *argumanlar, &blok)
end
def respond_to_missing?(ad, ozel = false) = @hedef.respond_to?(ad, ozel) || super
end
Bu blok de kaynaktır. method_missing üç şey alır: iletinin adı, argümanlar ve varsa blok. İlk satır, sarmalanan nesne o iletiyi anlamıyorsa super ile Ruby’nin varsayılan davranışına, yani NoMethodError’a bırakır. Anlıyorsa iletiyi aynı argümanlarla ve aynı blokla public_send ile ona iletir. respond_to_missing? ikinci yöntemdir; ne işe yaradığı birazdan ölçülecek.
Tanımsız Yöntemi Kim Yazdı
Bu dersin gözlemi Vekil nesnesinin sort yöntemidir. Nesne bir diziyi sarmalar; Vekil sınıfında sort adlı bir tanım yoktur.
# vekil.rb — Vekil#sort yontemini kim yazdi require_relative "yazanlar" vk = Vekil.new([3, 1, 2]) p yazan(vk, :sort) p vk.sort
["kimse", "tanim yok"] [1, 2, 3]
Yazan kimse: yazan yardımcısı alıcının zincirinde sort için bir tanım bulamadı ve ilk soruda durdu. Yine de vk.sort çalıştı ve [1, 2, 3] verdi. Yani çalışan bir yöntem var, ama yöntem arama zincirinde yok. Çalıştıran şey method_missing’dir; o, iletiyi dizinin kendi sort yöntemine iletir. Bu çelişki dersin konusudur: yazan kimse olabilir ve yanıt yine de gelir. MP19. Bu ölçüm tek bir ileti içindir; Vekil’in diğer iletileri aşağıda ayrıca ölçülür.
Yakalanan İletiler
Vekil yalnız sort’a değil, zincirinde tanımı olmayan ve sarmalanan dizinin genel yöntemi olan iletilere cevap verir. Aşağıdaki ölçüm üç ileti dener: biri argümansız, biri başka bir türde sonuç veren, biri blok alan.
# yakala.rb — vekilin iletisi yakalamasi
require_relative "yazanlar"
vk = Vekil.new([3, 1, 2])
p vk.sort
p vk.size
p vk.map { |x| x * 10 }
[1, 2, 3] 3 [30, 10, 20]
sort sıralı diziyi, size uzunluğu verdi. map blok aldı: method_missing bloğu &blok ile yakaladı ve public_send’e geçirdi, böylece dizi her elemanı on ile çarptı. Vekil, bu üç iletinin hiçbirini tanımlamadan cevap verdi. MP20. Bu ölçüm yalnız üç ileti içindir; zincirde tanımı olmayan dizi yöntemleri aynı şekilde geçer, ama bu üçünden fazlası burada görülmedi; zincirde tanımı olan adlar aşağıda ayrıca ölçülür.
Argümanlar ve Blok
Üç ileti method_missing imzasının yarısını gösterdi. Argümanların ve bloğun birlikte aktarılması ayrıca ölçülmeli, çünkü *argumanlar ve &blok yazılmazsa iletinin parçaları sessizce düşer.
# aktar.rb — argumanlarin ve blogun aktarilmasi
require_relative "yazanlar"
vk = Vekil.new([3, 1, 2])
p vk.first(2)
p vk.inject(10) { |toplam, x| toplam + x }
p vk.each_slice(2).to_a
[3, 1] 16 [[3, 1], [2]]
first(2) argümanı, inject(10) { ... } hem argümanı hem bloğu, each_slice(2) ise blok olmadan bir sayaç nesnesi döndürdü ve to_a onu açtı. Üçü de dizinin kendi çağrısıyla aynı sonucu verdi: vekil iletiyi bozmadan taşıdı. Taşıma public_send(ad, *argumanlar, &blok) satırının işi; o satırda *argumanlar ya da &blok eksik olsaydı bu çağrılarda argüman ya da blok iletiye ulaşmaz, sonuç ya da hata değişirdi; bu derste denenmedi. MP21. Üç çağrı ölçüldü; anahtar sözcük argümanları bu derste denenmedi.
Yansıma Ne Görür
Yansıma dersi, nesneye yöntemlerini sormanın yollarını verdi. Aşağıda vekile dört soru sorulur: respond_to?, defined?, method(:sort).owner ve sınıfın method_defined? sorusu.
# sorgu.rb — vekile yansima ile sormak require_relative "yazanlar" vk = Vekil.new([3, 1, 2]) p vk.respond_to?(:sort) p defined?(vk.sort) p vk.method(:sort).owner p Vekil.method_defined?(:sort) p yazan(vk, :size)
true "method" Vekil false ["kimse", "tanim yok"]
Cevaplar tutarsız görünür ve bunun gerekçesi respond_to_missing?’tir. Vekil yazılırken bu yöntem tanımlandığından respond_to? true verdi, defined? "method" döndürdü ve method(:sort) bir yöntem nesnesi üretti. Üretilen nesnenin sahibi Vekil çıktı, ama sınıfın tanım tablosuna sorulduğunda Vekil.method_defined?(:sort) false verdi. Yani respond_to_missing? yansımaya yakalamayı bildirir, ama tanım tablosuna bir kayıt eklemez. Yardımcı tanım tablosuna baktığı için size için de kimse dedi. Yansıma bir yönteme iki yoldan bakar: ileti cevaplanır mı, ve tanım var mı. Bu iki soru vekilde ayrışır. MP22. respond_to_missing? vekil tarafından tanımlandığı için bu cevaplar verildi; tanımlanmasaydı sonuç Yansımaya Bildirmek bölümünde ölçülür.
Vekil Dizi Değildir
Vekil bir diziyi sarmalar, ama dizi değildir. Dizi gibi davranan bir nesnenin dizi olduğunu varsaymak tehlikelidir; aşağıdaki ölçüm bunun sınırlarını çizer.
# kimlik.rb — vekil ve kimlik require_relative "yazanlar" vk = Vekil.new([3, 1, 2]) begin vk.sortt rescue NoMethodError => e puts e.class end p vk.is_a?(Array) p vk == [3, 1, 2] p vk.class p yazan(vk, :class)
NoMethodError false false Vekil ["ata", "Kernel"]
Yanlış yazılmış sortt iletisi NoMethodError verdi: method_missing sarmalanan dizinin o iletiyi anlamadığını gördü ve super ile Ruby’nin varsayılanına bıraktı. is_a?(Array) false çıktı; vekil dizi değil, diziyi tutan bir nesne. vk == [3, 1, 2] de false verdi, çünkü == zincirde tanımı olan bir yöntemdir ve vekil onu yakalamaz: yakalama yalnız zincirde bulunmayan adlar için çalışır. class iletisi de bu yüzden yakalanmadı, Vekil döndü; yazanı Ruby 4.0.7’de ata, Kernel. Vekilin yakaladığı iletiler, zincirde hiç tanımı olmayanlardır. MP23. Bu bölümde yalnız ==, is_a? ve class ölçüldü; to_s ayrı bir bölümde sınanır.
Zincirdeki Adlar Yakalanmaz
Vekil Dizi Değildir bölümü == ve class için yakalamanın çalışmadığını gösterdi. Aynı kural to_s gibi her nesnede bulunan başka adlar için de geçerli olmalı. Yanında, zincirde olmayan to_a ile karşılaştırılır.
# zincirdeki.rb — to_s ve to_a karsilastirmasi require_relative "yazanlar" vk = Vekil.new([3, 1, 2]) p vk.to_a p vk.to_s == [3, 1, 2].to_s p yazan(vk, :to_s) p yazan(vk, :to_a)
[3, 1, 2] false ["ata", "Kernel"] ["kimse", "tanim yok"]
to_a zincirde bulunmadığı için yakalandı ve diziyi verdi. to_s ise Ruby 4.0.7’de Kernel’den geldiği için yakalanmadı: vekilin metni dizinin metni değildir ve yazanı ata. İki ad dizi için aynı derecede olağan, ama vekil için biri kimse, öbürü ata çıktı. Bu fark vekilin sınırını çizer: sarmalanan nesnenin davranışını kopyalamak istiyorsanız, her nesnede hazır bulunan adlar ayrıca ele alınmalıdır. MP24. Ölçüm to_s ve to_a içindir; inspect ve hash gibi öbür hazır adlar aynı kurala uyar, ama ölçülmedi.
Yakalayıcının Kendi Yazanı
Şimdiye kadar yakalanan ada baktık. Yakalayan yöntemin kendisi için de aynı soru sorulabilir: method_missing kimin?
# yakalayici.rb — method_missing ve respond_to_missing? yazanlari require_relative "yazanlar" vk = Vekil.new([3, 1, 2]) p yazan(vk, :method_missing) p yazan(vk, :respond_to_missing?) p yazan(Object.new, :method_missing) p BasicObject.private_method_defined?(:method_missing)
["programci", "Vekil, def govdesi"] ["programci", "Vekil, def govdesi"] ["ata", "BasicObject"] true
Yakalanan sort kimse çıktı; yakalayan method_missing ise programcı: Vekil içinde def ile yazılmış, sahibi alıcının kendi sınıfı. Aynı soru sıradan bir Object.new için sorulunca Ruby 4.0.7’de yazan ata, BasicObject çıktı, ve son satır bu tanımın özel olduğunu söyledi. Yani her nesnenin zaten bir method_missing yöntemi vardır ve işi NoMethodError fırlatmaktır; Vekil bu yöntemi gölgeleyip kendi sürümünü koydu. Kimse yazanı bu yüzden bir boşluk değil, bir devir teslimdir: tanımı olmayan ad, tanımı olan bir yakalayıcıya düşer ve onun yazanı bellidir. MP25. Ölçüm yalnız Vekil ve Object için yapıldı; zincirde birden çok method_missing olduğunda super’ın hangisine ineceği bu derste sınanmadı.
Özel Yöntem Aktarılmaz
Vekil iletiyi public_send ile taşıdığı ve önce respond_to? ile sorduğu için sarmalanan nesnenin özel yöntemleri vekilden çağrılamaz. Bu bir tasarım sonucudur ve ölçülebilir.
# ozel.rb — ozel yontemin vekil uzerinden cagrilmasi require_relative "yazanlar" class Kutu def acik = "acik" private def sir = "sir" end vk = Vekil.new(Kutu.new) p vk.acik begin vk.sir rescue NoMethodError => e puts e.class end p vk.respond_to?(:sir) p vk.respond_to?(:sir, true)
"acik" NoMethodError false true
Genel acik yöntemi geçti, özel sir NoMethodError verdi. Son iki satır birbirinden ayrılır. respond_to?(:sir) false verdi, çünkü respond_to_missing? ikinci argümanı hedefe aktarır ve varsayılan hâlde hedefin yalnız genel yöntemleri sayılır. respond_to?(:sir, true) ise true verdi: özel yöntemler de sorulunca Kutu nesnesi sir’i tanıdı. Yine de vk.sir çalışmadı. Yansıma ile çağrı burada ayrışır: yansıma “bu ada cevap verilebilir” diyebilir, ama çağrıyı taşıyan public_send özel yöntemi yine reddeder. Vekil sarmaladığı nesnenin genel yüzünü taşır, tamamını değil; bu bazen istenen sonuçtur, çünkü sarmalama özel ayrıntıları dışarıya sızdırmaz. MP26.
Sessiz Yutma
method_missing yazarken super çağrısı unutulursa her ileti yakalanır ve hataya dönüşmez. Aşağıdaki sınıf super çağırmaz ve her iletiye nil döndürür.
# sessiz.rb — super cagirmayan method_missing class Sessiz def method_missing(*) = nil end s = Sessiz.new p s.yazim_hatasi p s.respond_to?(:yazim_hatasi)
nil false
yazim_hatasi adı diye bir yöntem hiçbir yerde yok, ama çağrı nil döndürdü ve hata vermedi. Hata bildirimi, adı doğru yazdığımıza dair son güvencemizdi ve burada yutuldu. Bu yüzden method_missing yazarken tanımadığı iletiyi super’a bırakmak bir kuraldır, bir tercih değil. Ayrıca respond_to? false verdi: bu sınıf iletiyi cevaplıyor, ama yansımaya bunu bildirmiyor. MP27. Bu sınıf her iletiyi yuttuğundan hiçbir ad hata vermez; gerçek bir sınıfta yakalama bir önek ya da sarmalanan nesne gibi bir koşula bağlanır.
Yansımaya Bildirmek
Yakalama koşullu olduğunda yansımayı da bilgilendirmek gerekir. Aşağıdaki sınıf bul_ öneki ile başlayan adları yakalar; önce respond_to_missing? olmadan, sonra onunla ölçülür.
# bulucu.rb — respond_to_missing? olmadan ve ile
class Bulucu
def method_missing(ad, *)
return super unless ad.start_with?("bul_")
ad.to_s.delete_prefix("bul_")
end
end
b = Bulucu.new
p b.bul_ece
p b.respond_to?(:bul_ece)
begin
b.method(:bul_ece)
rescue NameError => e
puts e.class
end
class Bulucu
def respond_to_missing?(ad, ozel = false) = ad.start_with?("bul_") || super
end
p b.respond_to?(:bul_ece)
p b.method(:bul_ece).call
"ece" false NameError true "ece"
bul_ece iletisi "ece" verdi, ama respond_to? false çıktı ve method(:bul_ece) NameError fırlattı. Yani iletiyi cevaplayan bir nesne, yansımaya göre o yöntemi tanımıyordu. Sınıf respond_to_missing? ile yeniden açılınca iki soru da düzeldi: respond_to? true, method(:bul_ece).call ise "ece" verdi. Kural şudur: method_missing yazan, respond_to_missing? da yazmalıdır. Aksi hâlde nesne yansımadan saklanır. MP28. Önek koşulu yalnız bul_ ile ölçüldü; respond_to_missing? ile yakalama koşulu aynı biçimde yazıldı, ayrı yazılırsa tutarsızlık doğabilir.
Sınırlayıcı Ölçüm
Kimse yazanı kalıcı mı? Aşağıdaki sınıf yakaladığı her adı ilk çağrıda define_method ile gerçek bir yönteme çevirir. Böylece yakalama yalnız bir kez çalışır.
# sinir.rb — kendini tanimlayan vekil
require_relative "yazanlar"
class Puan
attr_reader :sayac
def initialize
@sayac = 0
end
def method_missing(ad, *)
return super unless ad.start_with?("kare_")
@sayac += 1
n = ad.to_s.delete_prefix("kare_").to_i
self.class.define_method(ad) { n * n }
n * n
end
def respond_to_missing?(ad, ozel = false) = ad.start_with?("kare_") || super
end
pn = Puan.new
p [pn.kare_4, pn.kare_4, pn.kare_5, pn.sayac]
p yazan(pn, :kare_4)
p yazan(pn, :kare_9)
[16, 16, 25, 2] ["kod", "Puan, blok govdesi"] ["kimse", "tanim yok"]
kare_4 iki kez çağrıldı ama method_missing yalnız bir kez çalıştı: ilk çağrıda yöntem tanımlandı, ikincisi doğrudan tanıma ulaştı. sayac iki oldu, çünkü kare_4 ve kare_5 birer kez yakalandı. Sonra yazan kare_4’ü kod olarak sınıflandırdı; artık yöntem bir bloktan üretilmiş gerçek bir tanımdır. Hiç çağrılmamış kare_9 ise hâlâ kimse. Yani kimse yazanı kalıcı değil: aynı ad bir çağrıdan sonra koda dönüşebilir. Kategori adın değil, o anki tanım tablosunun özelliğidir. MP29. Tek bir sınıf ölçüldü; define_method’un self.class üzerinden çağrılması bir tasarım tercihidir ve tek örnek için tek yöntem tanımlamak da mümkündür.
Özet
- Zincirde tanımı olmayan bir ileti nesnenin
method_missingyöntemine teslim edilir;Vekilbu yolla sarmaladığı diziye gelen iletiyi iletir. - Yakalanan adların yazanı kimsedir:
yazanyardımcısı tanım tablosunda ad bulamaz, ama ileti yine cevaplanır; yakalayanmethod_missingise programcıdır veBasicObject’teki özel varsayılanı gölgeler. respond_to_missing?yansımaya yakalamayı bildirir;Vekil.method_defined?ise yinefalsekalır.superçağrısı olmayanmethod_missingher iletiyi yutar ve yazım hatalarını gizler.- Vekil, sarmaladığı türün yerine geçmez:
is_a?(Array),==veto_szincirdeki tanımlar olduğundan yakalanmaz; özel yöntemler depublic_sendile aktarılmaz. - Kimse yazanı kalıcı değildir: ilk çağrıda
define_methodile kendini tanımlayan vekilde aynı ad koda dönüşür.
Sonraki Adım
Bu dersin son gözlemi, bir adın önce kimse sonra kod olabildiğini gösterdi. Kursun kapanış dersi bu geçişi bilerek kurar: alan :fiyat, 0 gibi tek bir bildirim satırı, arkasında yöntem doğurur. Alan Özel Diller, bu bildirimin hangi yazanı gizlediğini sorar.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.