İçeriğe geç
academia.sh

Ders 06 / 09

Yansıma

Bir nesneye kendi yöntemlerini, durumunu ve türünü çalışma zamanında sormanın ve bir yöntemi kimin yazdığını bu sorularla sınıflandırmanın ölçümü.

Yayın Güncelleme

Bu derste

  • Bir nesneye yöntemlerini, örnek değişkenlerini ve türünü yansıma ile sorar
  • Bir yöntemin argüman sayısını ve parametrelerini yöntem nesnesinden okur
  • yazan yardımcısının hangi sorulardan kurulduğunu açıklar
  • Yansımanın Ruby ile yazılmamış yöntemler için neden yalnız kısmi cevap verdiğini ölçer
İçindekiler

Önceki ders, aynı iletinin aynı sınıfın iki örneğinde iki farklı yazana ulaşabildiğini gösterdi: tek bir nesnenin tekil sınıfı, yöntem arama zincirinin en önünde durur. O dersin son cümlesi bir soru bıraktı: Nokta’nın x yöntemini kimin yazdığını, dosyayı açıp bakmadan, nesnenin kendisine çalışma zamanında sorabilir miyiz? Bu ders o soruya cevap verir. Yansıma (reflection), bir programın kendi nesnelerini, sınıflarını ve yöntemlerini çalışma sırasında incelemesidir. Yöntemi çağırmak yerine yöntem hakkında soru sorulur.

Kursun ikinci yarısının konusu metaprogramlama (metaprogramming), yani program yazan ya da programın yapısını çalışırken değiştiren programdır. Yansıma onun salt okunur yarısıdır: yalnız sorar, hiçbir şeyi değiştirmez. Sonraki iki ders yazmaya geçer. Eksen aynı kalır: bu yöntemi kim yazdı? Dört olası yazan vardır: programcı, ata, kod ve kimse. Programcı yöntemi def ile kendisi yazmıştır. Ata, yöntemi üst sınıf ya da modül yazmıştır. Kod, yöntemi bir kod parçası üretmiştir; programcının elle yazdığı bir gövde yoktur. Kimse, o ada hiçbir tanım yoktur.

Sınıflandıran Yardımcı

Aşağıdaki dosya kursun ortak yardımcısıdır ve bu ders onu olduğu gibi verir. Üç soru sorar: alıcının zincirinde o ada bir tanım var mı, tanımı alıcının kendi sınıfı mı taşıyor, tanım bir def gövdesine mi dayanıyor. Her soru bir yansıma çağrısıdır; sırayla bakacağız.

# 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, çıktısı yoktur; yalnız diske yazılır ve diğer dosyalar onu require_relative ile çeker. Yardımcı yalnız biçimi okur: tanımın nerede durduğuna ve gövdesinin hangi türden olduğuna bakar. Yazarın niyetini okumaz. MP1. Bu sınıflandırma yalnız Ruby ile yazılmış kullanıcı sınıflarında anlamlıdır; çekirdek sınıflar için sonuç bu dersin sonunda ölçülür.

Ölçülen Sınıflar

Ders boyunca ölçülen nesneler aşağıdaki dosyadadır. Kursun ortak tanımlarından bu dersin gözlemine gereken kesit alınmıştır: bir hesap sınıfı, onun alt sınıfı, bir modül ve bir Struct.

# yazanlar.rb — bu dersin gozlemi icin gereken tanimlar
require_relative "yazan"

module Raporlanir
  def rapor = "rapor: #{bakiye_metni}"
end

class Hesap
  include Raporlanir

  def initialize(sahip, bakiye)
    @sahip = sahip
    @bakiye = bakiye
  end

  def bakiye_metni = "#{@sahip} #{@bakiye}"
  def net = @bakiye - komisyon

  private

  def komisyon = 2
end

class Vadeli < Hesap
end

Nokta = Struct.new(:x, :y)

Struct, verilen adlarla bir sınıf üreten çekirdek sınıftır; her ad bir üye (member) olur ve o üye için okuyucu ile yazıcı yöntemler hazır gelir. Nokta böyle üretilmiş bir sınıftır. Bu blok da kaynaktır.

Üye Okuyucusunu Kim Yazdı

Kursun bu dersteki gözlemi Nokta nesnesinin x yöntemidir. Aşağıdaki dosya yardımcıyı çağırır ve yöntemin çalışan değerini yanına basar.

# uye_okuyucu.rb — Nokta#x yontemini kim yazdi
require_relative "yazanlar"

n = Nokta.new(3, 4)
p yazan(n, :x)
puts n.x
["kod", "Nokta, ruby govdesi yok"]
3

Yazan kod: x yöntemini ne programcı def ile yazdı ne bir üst sınıf; Struct.new çağrısı onu sınıf üretilirken yarattı. Yardımcı bunu iki şeyden çıkardı. Yöntemin sahibi Nokta‘nın kendisi çıktı, yani ata değil. Ruby gövdesi de yoktu, çünkü RubyVM::InstructionSequence.of bu yöntem için nil döndürdü. Hangi sorunun hangi cevabı verdiği sıradaki bölümlerde tek tek ölçülür. MP2. Tek bir Struct sınıfı Ruby 4.0.7’de ölçüldü; Struct’ın başka bir sürümde üyeleri farklı yazması bu ölçümden çıkarılamaz.

Sınıfa ve Nesneye Sormak

Yansımanın ilk grubu sınıfın ve nesnenin yapısını sorar: hangi üyeler var, üst sınıf kim, nesnenin kendi durumunda ne var.

# sorgu.rb — Nokta nesnesine yansima ile sormak
require_relative "yazanlar"

n = Nokta.new(3, 4)
p Nokta.members
p n.to_a
p Nokta.superclass
p yazan(n, :to_a)
p yazan(n, :x=)
p n.instance_variables
[:x, :y]
[3, 4]
Struct
["ata", "Struct"]
["kod", "Nokta, ruby govdesi yok"]
[]

members üye adlarını sıralı döndürdü, to_a değerleri aynı sırayla döndürdü. superclass Nokta’nın Struct’tan türediğini söyledi. İki yazan çağrısı iki farklı sonuç verdi: to_a yöntemini Struct yazmıştır, çünkü sahip Nokta değil; yazan ata. x= yöntemini ise Nokta taşıyor ve gövdesi yok; yazan kod. Aynı nesnede iki yöntem iki farklı yazana ulaştı. Son satır şaşırtıcıdır: instance_variables boş çıktı. Struct değerlerini örnek değişkenlerinde değil, kendi iç düzeninde tutar; yansıma bu yüzden @x bulamaz. MP3. Boş liste yalnız Struct için ölçüldü; sıradan bir sınıfın örnek değişkenleri bir sonraki blokta görülür.

Durumu ve Özel Yöntemi Sormak

Sıradan bir sınıfın nesnesinde durum örnek değişkenlerinde durur. Yansıma onları adıyla listeler, değerini adıyla okur. Özel (private) yöntemler ise respond_to? ikinci argüman true ile sorulursa görünür.

# durum.rb — nesnenin durumunu ve ozel yontemini sormak
require_relative "yazanlar"

h = Hesap.new("ece", 100)
p h.instance_variables
p h.instance_variable_get(:@bakiye)
p h.respond_to?(:komisyon)
p h.respond_to?(:komisyon, true)
p h.respond_to?(:net)
[:@sahip, :@bakiye]
100
false
true
true

instance_variables iki örnek değişkenini oluşturulma sırasıyla verdi. instance_variable_get(:@bakiye) değeri okudu; bu çağrı, dışarıdan okuyucu yöntem tanımlanmamış olsa da çalışır, çünkü örnek değişkenlere yansıma yoluyla doğrudan erişir. respond_to?(:komisyon) false verdi, çünkü komisyon özeldir ve varsayılan soru yalnız herkese açık yöntemleri kapsar. İkinci argüman true verilince aynı soru true döndü. net herkese açıktır ve true döndü. Yani varsayılan respond_to? sorusu yöntemin var olup olmadığını değil, çağıran için görünür olup olmadığını sorar; ikinci argüman true verilince özel yöntemler de kapsama girer. MP4. Özel yöntem sorusu yalnız respond_to? ile ölçüldü; private_methods gibi diğer sorgular bu derste kullanılmadı.

Yöntemin Kendisine Sormak

Yansıma bir yöntemi nesne olarak da yakalar. instance_method sınıftan, method ise nesneden bir yöntem nesnesi verir. Yöntem nesnesi argüman sayısını, parametrelerini, tanımlandığı yeri sorgulatır ve çağrılabilir.

# yontem.rb — yontemin kendisine sormak
require_relative "yazanlar"

h = Hesap.new("ece", 100)
p Hesap.instance_method(:net).arity
p Hesap.instance_method(:initialize).parameters
p h.method(:net).call
p Hesap.instance_method(:net).source_location.nil?
p Nokta.instance_method(:x).source_location.nil?
p Object.const_get(:Hesap)
0
[[:req, :sahip], [:req, :bakiye]]
98
false
true
Hesap

Argüman sayısı (arity) net için 0 çıktı: yöntem argüman almıyor. parameters, initialize’ın iki zorunlu parametresini adlarıyla listeledi; :req, zorunlu demektir. h.method(:net).call yöntemi yakalayıp çağırdı ve 98 verdi: 100 eksi özel komisyon’un 2’si. source_location yöntemin dosya ve satırını verir; yol ortama bağlı olduğundan burada yalnız boş olup olmadığı basıldı. Hesap#net için boş değil, çünkü def ile yazılmış; Nokta#x için boş, çünkü Struct onu yazmış ve ortada bir kaynak satırı yok. Bu, yazan’ın nil kontrolünün gözlemlenebilir karşılığıdır. Son satır, sabiti adından bulur: const_get(:Hesap) sınıfın kendisini döndürdü. MP5. source_location yalnız iki yöntem için ölçüldü; nil olmaması yazanın programcı olduğunu tek başına göstermez.

Sahibi Sormak

yazan yardımcısının kalbi tek bir soru: yöntem nesnesinin sahibi kim. Sahip, yöntemin tanımının durduğu sınıf ya da modüldür. Sahibi bulmak için yardımcı alıcının sınıf zincirine bakar; aynı zinciri elle de listeleyebiliriz.

# sahip.rb — yontemin sahibi ve alicinin zinciri
require_relative "yazanlar"

h = Hesap.new("ece", 100)
v = Vadeli.new("a", 1)
p Vadeli.ancestors.first(3)
p h.singleton_class.ancestors.drop(1).first(3)
p h.method(:rapor).owner
p Vadeli.instance_method(:net).owner
p yazan(v, :net)
p yazan(h, :rapor)
[Vadeli, Hesap, Raporlanir]
[Hesap, Raporlanir, Object]
Raporlanir
Hesap
["ata", "Hesap"]
["ata", "Raporlanir"]

ancestors sınıfın ata zincirini (ancestors) soldan sağa verir: önce sınıfın kendisi, sonra include edilen modüller ve üst sınıflar, yöntem arama zincirinin okunma sırasıyla. Vadeli için ilk üçü Vadeli, Hesap ve Raporlanir oldu: modül, include edildiği sınıfın hemen arkasında durur. Nesnenin kendi tekil sınıfının zinciri tekil sınıfın kendisiyle başlar; o ad her koşumda değiştiğinden basılmaz, ilk eleman atılır ve kalan zincir Hesap, Raporlanir, Object olarak görünür. rapor yönteminin sahibi Raporlanir çıktı, Vadeli.instance_method(:net) ise Vadeli’nin değil Hesap’ın tanımına ulaştı. Bu yüzden yazan(v, :net) ve yazan(h, :rapor) ikisi de yazanı ata olarak sınıflandırdı: sahip, alıcının sınıfı ya da tekil sınıfı değil. MP6. Zincir yalnız ilk üç elemanla basıldı; Object’ten sonraki kuyruk ölçülmedi.

Tek Nesnenin Yöntemi

Önceki dersin gözlemine dönelim: aynı sınıfın iki örneğinde aynı ileti iki yazana ulaşabilir. Yansıma bunu da sorar. singleton_methods, nesnenin yalnız kendi tekil sınıfında tanımlı yöntemlerini listeler. Aşağıdaki ölçüm iki Hesap nesnesi kurar ve yalnız birine kendi net yöntemini yazar.

# tekil.rb — iki ornekte ayni ileti
require_relative "yazanlar"

a = Hesap.new("ece", 100)
b = Hesap.new("can", 50)
def b.net = 0

p a.singleton_methods
p b.singleton_methods
p a.net
p b.net
p yazan(a, :net)
p yazan(b, :net)
[]
[:net]
98
0
["programci", "Hesap, def govdesi"]
["programci", "tekil sinif, def govdesi"]

a için liste boş, b için net var. Aynı sınıfın iki örneği aynı iletiye iki farklı değerle cevap verdi: a için 98, b için 0. İki yazan çağrısı farkı adlandırır. a.net yöntemini Hesap sınıfındaki def yazmıştır; b.net yöntemini ise yine bir def yazmıştır, ama bu kez tanım b’nin tekil sınıfında durur ve yardımcı bunu sahip adı yerine tekil sınıf diye anar. Yazan her iki durumda programcıdır; fark tanımın yerindedir. Yansıma bu yüzden iki ayrı soruyu ayırır: yöntem kimin tarafından üretildi ve hangi katmanda duruyor. MP7. Yalnız net yöntemi iki örnekte ölçüldü; tekil yöntemin sınıf yöntemiyle ilişkisi bu derste kurulmadı.

Tür ve Yöntem Listesi

Son grup, nesnenin hangi türden olduğunu ve bir sınıfın kendi yöntemlerinin listesini sorar. Liste bir kez de sınıf yeniden açıldıktan sonra alınır: yeniden açılan sınıfa attr_reader eklenir ve liste değişir.

# tur.rb — nesnenin turunu ve sinifin yontemlerini sormak
require_relative "yazanlar"

h = Hesap.new("ece", 100)
v = Vadeli.new("a", 1)
p h.is_a?(Raporlanir)
p v.is_a?(Hesap)
p v.instance_of?(Hesap)

class Hesap
  attr_reader :sahip
end

p Hesap.public_instance_methods(false).sort
p yazan(h, :sahip)
true
true
false
[:bakiye_metni, :net, :sahip]
["kod", "Hesap, ruby govdesi yok"]

is_a? zincirdeki her sınıf ve modül için true döndürür: h Raporlanir’a, v de Hesap’a bağlıdır. instance_of? ise yalnız tam sınıfı kabul eder; Vadeli örneği için Hesap sorusu false çıktı. public_instance_methods(false) yalnız Hesap’ın kendi herkese açık yöntemlerini listeler; false, atalardan gelenleri dışarıda bırakır. Bu yüzden Raporlanir’dan gelen rapor listede yok, initialize de yok, çünkü initialize özeldir. attr_reader :sahip listeye sahip’i ekledi. Ve yazan onun yazanını kod olarak sınıflandırdı: attr_reader bir def gövdesi bırakmaz, yöntemi kendisi üretir. MP8. Liste, yeniden açma anından sonra alındı; sınıfın başka yerde yeniden açılmadığı varsayılır.

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

Şimdiye kadar yansıma, programcının yazdığı sınıflarda iş gördü. Yardımcı çekirdek sınıflara çevrildiğinde ne olur?

# sinir.rb — kuralin sinirlari
require_relative "yazanlar"

p yazan("a", :upcase)
p yazan(Hesap, :new)
[5, :a].each do |deger|
  begin
    deger.singleton_class
  rescue => e
    puts "#{deger.inspect} -> #{e.class}"
  end
end
p nil.singleton_class
["kod", "String, ruby govdesi yok"]
["kod", "Class, ruby govdesi yok"]
5 -> TypeError
:a -> TypeError
NilClass

String#upcase ve Class#new için yardımcı kod dedi, ama bu cevap yanıltır. Bu yöntemleri Ruby’nin kendi çekirdeği derlenmiş kodla yazdı; ortada bir def gövdesi olmadığından kural onları, Struct üyeleriyle aynı gerekçeyle koda atadı. Aralarındaki fark yardımcıya görünmez: biri çalışma zamanında üretilmiştir, diğeri çekirdekte baştan yazılmıştır. Kural biçimi okur, niyeti okumaz. İkinci sınır daha sertti: tamsayının ve Symbol değerinin tekil sınıfı yok, ikisi de TypeError verdi. nil ise NilClass döndürdü, yani nil için yardımcı çalışır. Bu üç sonuçtan çıkan sınır şudur: yansıma, tanımın nerede durduğunu doğru söyler, yazarın kim olduğunu her zaman söylemez. MP9. Çekirdek sınıflardaki iki sonuç yalnız bu iki yöntem için ölçüldü; başka çekirdek yöntemleri farklı çıkabilir.

Özet

  • Yansıma, nesneye yöntemini, durumunu ve türünü çalışma zamanında sormaktır; members, instance_variables, respond_to?, is_a? ve public_instance_methods(false) böyle sorulardır.
  • Nokta#x yöntemini kod yazdı: Struct.new onu sınıf üretilirken yarattı, ortada def gövdesi yok.
  • Nokta#to_a yöntemini ata yazdı: sahip Struct.
  • Yöntem nesnesi arity, parameters ve source_location ile sorgulanır ve call ile çağrılır.
  • attr_reader ile eklenen okuyucu da kod tarafından yazılmış sayılır.
  • Yardımcı yalnız Ruby ile yazılmış kullanıcı sınıflarında anlamlı sonuç verir; ölçülen iki çekirdek yöntem (String#upcase, Class#new) koda gitti.

Sonraki Adım

Bu derste kod, bir sınıfın yöntemini sınıf tanımlanırken üretti ve biz üretimi yalnız sonuçtan okuduk. Sıradaki ders üretimin kendisini yapar: define_method, ad çalışma zamanında belirlenen bir yöntemi bir blokla tanımlar. Aynı soru, bu kez yazan olarak kodun nasıl ortaya çıktığını 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