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, 0satırı bir çağrıdır; doğurduğufiyatyöntemini yazan koddur, çünküdefine_methodbir blok gövdesi üretir.- Bildirim yöntemi
alaniseModel’de yazılmıştır ve sınıf yöntemi olduğundanUrun’un tekil sınıf zincirinde aranır; o yöntemin yazanı atadır. instance_evalbloğunself’ini değiştirir; dış örnek değişkenlerinilolur, yerel değişkenler görünür kalır.yieldile aktarılan nesneself’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.