İçeriğe geç
academia.sh

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_missing yöntemine teslim edilir; Vekil bu yolla sarmaladığı diziye gelen iletiyi iletir.
  • Yakalanan adların yazanı kimsedir: yazan yardımcısı tanım tablosunda ad bulamaz, ama ileti yine cevaplanır; yakalayan method_missing ise programcıdır ve BasicObject’teki özel varsayılanı gölgeler.
  • respond_to_missing? yansımaya yakalamayı bildirir; Vekil.method_defined? ise yine false kalır.
  • super çağrısı olmayan method_missing her iletiyi yutar ve yazım hatalarını gizler.
  • Vekil, sarmaladığı türün yerine geçmez: is_a?(Array), == ve to_s zincirdeki tanımlar olduğundan yakalanmaz; özel yöntemler de public_send ile aktarılmaz.
  • Kimse yazanı kalıcı değildir: ilk çağrıda define_method ile 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.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat