İçeriğe geç
academia.sh

Ders 09 / 09

Alan Özel Diller

Bildirim satırının arkasında yöntemi kimin yazdığı ölçülür: define_method ile üretilen erişimci koddur, instance_eval ise self'i değiştirir ama yerel değişkenleri değiştirmez. Ders, kursun dokuz gözleminin kapanış tablosunu da verir.

Yayın Güncelleme

Bu derste

  • Bir bildirim satırının arkasında define_method ile yöntem doğurduğunu ve bu yöntemin yazanının kod olduğunu ölçer
  • Sınıf düzeyindeki bildirim yönteminin tekil sınıf zincirinde arandığını ve yazanının ata olduğunu gösterir
  • instance_eval ile bloklu bir alan özel dil kurar ve bloğun self'ini ölçer
  • Kursun dokuz gözlemini yazan kategorisine göre tabloda toplar
İçindekiler

Önceki ders, bir adın önce kimse sonra kod olabildiğini gösterdi. Bu ders aynı geçişi bilerek kullanır: alan :fiyat, 0 gibi tek bir satır okunduğunda bir çağrıya benzer, ama çalıştığında sınıfa yeni bir yöntem ekler. Alan özel dil (domain-specific language), belirli bir iş için küçültülmüş ve o işin sözcükleriyle konuşan bir yazım biçimidir. Ruby’de bunun bir kurulumu, sınıf gövdesinde çağrılan bildirim yöntemleridir. Satırın kısalığı, ölçülmesini gereksiz kılmaz; tersine, bu satırların arkasında yöntemi kimin yazdığı ilk bakışta belli değildir.

Eksen kursun başından beri aynı: bu yöntemi kim yazdı? Bu dersin yazanı kod. alan :fiyat, 0 satırını programcı yazdı, ama bu satırın doğurduğu fiyat yöntemini yazan Ruby kodudur, yani bir define_method çağrısı. Aşağıdaki ölçümler bu ayrımı çizer; sonda bloklu biçimin nerede kırıldığı gösterilir. yazan yardımcısı önceki derslerdeki hâliyle 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

Gözlem iki sınıf üzerindedir. Model, bildirim yöntemini taşır; Urun onu kullanır.

# model.rb — bildirim yontemi tasiyan taban sinif
require_relative "yazan"

class Model
  def self.alan(ad, varsayilan)
    define_method(ad) { instance_variable_get("@#{ad}") || varsayilan }
  end
end

class Urun < Model
  alan :ad, "adsiz"
  alan :fiyat, 0
end

Bu blok de kaynaktır. alan, adı ve bir varsayılan değeri alır; ad için bir okuyucu yöntem tanımlar. Okuyucu, örnek değişkeni doluysa onu, boşsa varsayılanı verir. Urun gövdesindeki iki satır çağrıdır, tanım değildir: sınıfta tek bir def yoktur.

Bildirim Satırı Neyi Yazdı

Dersin gözlemi Urun.new.fiyat iletisidir. Önce yöntemin ne verdiği, sonra kimin yazdığı ve sınıfta nelerin göründüğü ölçülür.

# olcum.rb — bildirim satirinin dogurdugu yontem
require_relative "model"

u = Urun.new
p u.fiyat
p u.ad
p yazan(u, :fiyat)
p Urun.instance_methods(false).sort
0
"adsiz"
["kod", "Urun, blok govdesi"]
[:ad, :fiyat]

Yöntem 0 verdi, çünkü örnek değişkeni boştu ve varsayılan devreye girdi. Yazan kod çıktı: yazan tanımı Urun’un kendi zincir halkasında buldu ve iseq etiketi bir blok gövdesine işaret etti. Sınıfın kendi yöntem listesi [:ad, :fiyat]; iki bildirim satırı iki yöntem doğurdu. Programcının yazdığı yalnız iki satırdır, ama sınıfın arayüzü iki yöntemdir. MP30. Bu ölçüm tek bir bildirim yöntemi ve iki alan içindir; alan başka bir gövdeyle yazılsaydı yazan değişebilirdi.

Çağrı Ama Yazan Kod

Yöntemin gerçekten okuduğu şey örnek değişkenidir. Değişken doldurulunca varsayılan geride kalmalıdır.

# deger.rb — varsayilan ve doldurulmus deger
require_relative "model"

u = Urun.new
u.instance_variable_set(:@fiyat, 40)
p u.fiyat
p Urun.new.fiyat
p yazan(Urun, :alan)
40
0
["ata", "#<Class:Model>"]

Dolu değişken varsayılanı geçti: 40. Aynı sınıfın yeni nesnesi ise 0 verdi, çünkü varsayılan sınıfa, değer nesneye aittir. Son satır sahibi alıcıdan ayırır: alan yöntemini yazan ata çıktı ve yer olarak Model’in tekil sınıfı göründü. Alıcı olarak Urun sınıfı verildi; alan tanımı Urun’un kendi tekil sınıfında değil, Model’inkinde duruyor. Sahip alıcının tekil sınıfı olmadığı için kural onu atadan saydı; yani aynı bildirim satırında iki ayrı yazan çalışır. alan çağrısının yöntemini ata yazdı, o çağrının doğurduğu fiyat yöntemini kod yazdı. MP31. Bu ayrım yazan yardımcısının tekil sınıf kurallarına dayanır; başka bir yardımcı farklı sınıflandırabilir.

Bu, M08/K06’daki soruyla karıştırılmamalıdır. Orada bir davranışın kaynaktaki sebebi soruldu: çağrı mı, açıklama mı, varsayılan mı. Burada sebep sorulmaz, tanımın yazarı sorulur: alan :fiyat, 0 bir çağrıdır, ama doğurduğu yöntemi yazan koddur.

Okuma Durum Bırakmaz

Üretilen yöntemin bir yan etkisi olup olmadığı da ölçülebilir. Varsayılan, yöntemin içinde hesaplanır; nesneye yazılmaz.

# okuma.rb — okuyucunun nesnede biraktigi iz
require_relative "model"

u = Urun.new
p u.fiyat
p u.instance_variables
p u.method(:fiyat).owner
p u.method(:fiyat).arity
0
[]
Urun
0

fiyat okundu ve 0 verdi, ama nesnenin örnek değişkenleri listesi boş kaldı: varsayılan bir değer olarak döndü, bir alan olarak saklanmadı. Yöntemin sahibi Urun, yani bildirim yöntemin tanımını alt sınıfa koydu; Model’e değil. Argüman sayısı sıfır: define_method ile verilen blok parametre almadığı için üretilen okuyucu da almaz. Bu üç gözlem, bildirimsel arayüzün sınıfa ne yazdığını tamamlar: yeni bir yöntem, sahibi alt sınıf, durumu yok. MP32. Bu ölçüm yalnız okuma yönündedir; değeri yazan bir yöntem bu alan tanımında yok, değer yalnız instance_variable_set ile verildi.

Sınıf Yöntemi Nerede Aranır

Urun.alan çağrısının neden Model’e ulaştığı tekil sınıf zincirinden okunur.

# zincir.rb — sinif yontemlerinin zinciri
require_relative "model"

p Urun.singleton_class.ancestors.take(3)
p Model.singleton_class.instance_methods(false)
p Urun.respond_to?(:alan)
p Urun.new.respond_to?(:alan)
[#<Class:Urun>, #<Class:Model>, #<Class:Object>]
[:alan]
true
false

Zincirin ilk halkası Urun’un kendi tekil sınıfı, ikincisi Model’inkidir. Sınıf yöntemleri kalıtımla gelir çünkü alt sınıfın tekil sınıfı, üst sınıfın tekil sınıfından türer. Urun.alan bu zincirde ikinci halkada bulundu. Bildirim yöntemini tabanda yazmak bu yüzden yeter: her alt sınıf onu kendi gövdesinde çağırabilir. Model’in tekil sınıfında tek yöntem var, alan. Urun sınıfı ona cevap verir (true), ama Urun’un bir nesnesi vermez (false): bildirim sınıfın arayüzüne aittir, nesnenin arayüzüne değil. Nesne yalnız bildirimin doğurduğu ad ve fiyat yöntemlerini görür. Bu ayrım alan özel dilin neden sınıf gövdesinde yaşadığını da söyler: bildirim yazılırken konuşulan şey nesne değil, sınıftır. MP33. Çıktı yalnız ilk üç halkayı gösterir; zincirin devamı bu derste ölçülmedi.

Bloklu Alan Özel Dil

Bildirimleri sınıf gövdesinin dışına çıkarmak için başka bir kurulum vardır: bir blok verilir ve blok bir nesnenin bağlamında çalıştırılır.

# form.rb — instance_eval ile bloklu biçim
class Form
  attr_reader :alanlar

  def self.kur(&blok)
    f = new
    f.instance_eval(&blok)
    f
  end

  def initialize
    @alanlar = []
  end

  def alan(ad)
    @alanlar << ad
  end
end

f = Form.kur do
  alan :ad
  alan :eposta
end
p f.alanlar
[:ad, :eposta]

Blok içindeki alan çağrılarının alıcısı yazılmadı; instance_eval bloğu Form nesnesinin bağlamında çalıştırdı, yani bloğun self’i o nesne oldu. Sonuç [:ad, :eposta]. Bu biçim okunaklıdır: kullanıcı nesne adı yazmadan yalnız sözcükleri yazar. Okunaklılığın bir bedeli var ve aşağıda ölçülür. MP34. Burada tek bir alan yöntemi ve iki çağrı vardı; bloğun içinde başka yöntem çağrıları bu ölçümde yok.

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

instance_eval bloğun self’ini değiştirir. Peki bloğun dışındaki değişkenlere ne olur? Üç değişkene bakılır: dışarıda tanımlı bir örnek değişkeni, bir yerel değişken ve self’in kendisi. Sonra aynı kurulumun yield ile yazılmış biçimi denenir.

# sinir.rb — instance_eval ve dis degiskenler
@baslik = :kayit
yerel_ad = :yerel

class Form
  attr_reader :alanlar

  def self.kur(&blok)
    f = new
    f.instance_eval(&blok)
    f
  end

  def initialize
    @alanlar = []
  end

  def alan(ad)
    @alanlar << ad
  end
end

class Form2
  attr_reader :alanlar

  def self.kur
    f = new
    yield f
    f
  end

  def initialize
    @alanlar = []
  end

  def alan(ad)
    @alanlar << ad
  end
end

p Form.kur { alan @baslik }.alanlar
p Form.kur { alan yerel_ad }.alanlar
p Form.kur { alan self.class }.alanlar
p Form2.kur { |f| f.alan @baslik }.alanlar
[nil]
[:yerel]
[Form]
[:kayit]

Üç sonuç var. Dışarıdaki @baslik blok içinde nil verdi: örnek değişkenleri self’e bağlıdır ve self değişti. Yerel değişken yerel_ad ise [:yerel] verdi: yerel değişkenler bloğun kapanışıyla yakalanır, self’e bağlı değildir. self.class Form çıktı. Son satırdaki yield biçimi self’i korudu ve @baslik değeri [:kayit] olarak okundu. instance_eval böylece sözcük kazandırır ama dışarıdaki örnek değişkenlerini görünmez yapar. MP35. Bu fark yalnız örnek değişkenleri için ölçüldü; sabitlerin ve sınıf değişkenlerinin davranışı bu derste sınanmadı.

Bildirimin bir de sessiz bedeli vardır: aynı alanı iki kez bildirmek hata vermez.

# tekrar.rb — ayni alanin iki kez bildirilmesi
require_relative "model"

class Etiket < Model
  alan :ad, "adsiz"
  alan :ad, "yeni"
end

p Etiket.new.ad
p Etiket.instance_methods(false)
"yeni"
[:ad]

İkinci bildirim birincinin üzerine yazdı: ad artık "yeni" veriyor ve sınıfta hâlâ tek ad yöntemi var. Hiçbir uyarı, hiçbir istisna yok. Bildirimsel arayüz bu yüzden kolay yazılır ve kolay yanlış yazılır; define_method aynı adı sessizce yeniden tanımlar. MP36. Bu ölçüm iki bildirimin aynı sınıfta olduğu durumdur; ikisi farklı kalıtım düzeylerinde olsaydı alt sınıf üst sınıfın yöntemini gölgelerdi.

Özet

  • alan :fiyat, 0 satırı bir çağrıdır; doğurduğu fiyat yöntemini yazan koddur, çünkü define_method bir blok gövdesi üretir.
  • Bildirim yöntemi alan ise Model’de yazılmıştır ve sınıf yöntemi olduğundan Urun’un tekil sınıf zincirinde aranır; o yöntemin yazanı atadır.
  • instance_eval bloğun self’ini değiştirir; dış örnek değişkenleri nil olur, yerel değişkenler görünür kalır.
  • yield ile aktarılan nesne self’i korur; sözcük kazancı için dış örnek değişkenlerinden vazgeçilir.
  • Aynı alanın iki kez bildirilmesi hata vermez; sonraki bildirim sessizce önceki yöntemin yerine geçer.
  • Bu dersin yazanı kod: bildirimsel arayüzün satırları programcıya, doğurdukları yöntemler koda aittir.

Kurs Kapanışı

Kursun her dersi aynı soruyu sordu: bir yöntemi kim yazdı? Dokuz gözlem, dokuz kez bu soruyu bir ölçümle yanıtladı. Aşağıdaki tablo hepsini yazan kategorisine göre toplar.

Ders Ölçülen yöntem Yazan Sınırlayıcı ölçüm
Sınıf Tanımı ve Örnek Değişkenler hesap.bakiye_metni programcı attr_reader aynı sınıfta ikinci yazan
Kalıtım ve super Vadeli.new("can", 50).bakiye_metni ata super çağıran yeniden tanım programcı
Modüller ve Karışımlar hesap.rapor ata sınıfın def’i karışımı gölgeler
Görünürlük komisyon (hesap.net içinden) programcı public :komisyon tanımın sahibini alt sınıfa taşır
Yöntem Arama Zinciri Kasa.new.ac ata tek nesnede tekil yöntem başa girer
Yansıma Nokta.new(3, 4).x kod çekirdek yöntemleri de kod görünür
Dinamik Yöntem Tanımı Ayar.new.renk kod class_eval dizgisi programcı çıkar
Eksik Yöntem Yakalama Vekil.new([3, 1, 2]).sort kimse ilk çağrıda kimse koda dönüşür
Alan Özel Diller Urun.new.fiyat kod instance_eval self’i değiştirir

Yazanların dağılımı: 3 ata · 3 kod · 2 programcı · 1 kimse. Tablo kursun bir bulgusunu taşır: yazan sınıf başına değil yöntem başına ölçüldü, ve bir yöntemin yazanı kaynak metinde nasıl göründüğüyle değil zincirde nasıl bulunduğuyla belirlendi. Aynı sınıfta iki yazan, aynı satırda iki yazan göründü.

İki çıkarım yapılabilir. Birincisi, yazanı bilmek yazım biçimini bilmeyi gerektirmez: attr_reader ve define_method ayrı sözcüklerdir ama ikisi de kodu yazan yapar; def ve class_eval dizgisi ayrı sözcüklerdir ama ikisi de programcıyı. İkincisi, yansıma yazanı söylemek için yeterli değildir: source_location, owner ve respond_to? ayrı ayrı yanıltıcı olabilir; kural bunları birlikte okudu. Kimse kategorisi tek örneğe düştü ve o da kalıcı olmadı. Üçüncü bir çıkarım dağılımın kendisinden gelir: dokuz gözlemin yalnız ikisinde yazan programcıdır. Ölçülen sınıflarda arayüzün büyük kısmı atadan, karışımdan ve kodun ürettiği tanımlardan geldi; sınıf gövdesine bakmak bu yüzden yazanı bulmak için tek başına yetmez, zincirin ve tanım biçiminin ölçülmesi gerekir.

MP37. Tablo yalnız dokuz ölçümün sınıflandırmasıdır; kategorilerin sayısı ölçülen yöntemlerin seçimini yansıtır, Ruby’deki yöntemlerin genel dağılımını değil. Yazan hücreleri her dersin Özet bölümündeki ifadeden alındı.

Kurs burada kapanır. Sıradaki kurs, Ruby ile yazılmış bir web uygulamasının dört katmanını sırayla ele alır: yönlendirme, denetleyici, model ve görünüm. Orada da aynı kural geçerli kalır: tahmin yürütülmez, ölçülür.

İ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