Ders 10 / 11
Statik Tip Denetimi
Ders içinde modellenen denetleyici beş çağrının iki tip ihlalini bulur ve iki alan ihlalini göremez; buna karşılık kaynaktaki üç çağrı yerinin üçünü de görür, oysa çalışma zamanı yalnız birine uğrar.
İçindekiler
Önceki ders açıklamayı okuyan tarafın tek bir adımını modelledi: bir çağrı yerinin dönüş tipini çıkarmak. Ölçüm, genellikli yazımın iki ihlali de yanlış alarmsız bildirebildiğini gösterdi — ama bildirimi yapan bir program yoktu, yalnız bir çıkarım kuralı vardı.
Bu ders o programı kurar. Kaynağı ya da açıklamaları okuyup çağrıları imzayla karşılaştıran ve uyuşmayanları çalıştırmadan bildiren bir katmana statik tip denetimi (static type checking) denir. Sorulan sayı üç parçalıdır: bu katman ilk dersteki dört ihlalin kaçını bulur, kaçını bulamaz, ve bulamadıklarını neden bulamaz?
Denetleyici Bu Derste Modellenmiştir
Bir uyarı baştan: bu derste kullanılan denetleyici ders içinde yazılmıştır. Hazır bir araç çalıştırılmaz ve hiçbir aracın adı geçmez. Ölçülen şey belirli bir programın davranışı değil, yaklaşımın kendisinin sınırıdır — ve o sınır, denetleyicinin elindeki bilgiden çıkar. Modellenmiş denetleyici o bilgiyi aynen kullanır: açıklamaları okur, çağrının argümanlarına bakar, uyuşmazlıkları listeler.
Ölçüm iki denetleyici biçimi kullanıyor. Birincisi çağrıların argümanlarını doğrudan alır; argümanlar kaynağa yazılmış sabitler olduğu için bu, kaynağı okumakla aynı bilgiyi verir. İkincisi işi daha açık yapar: kaynak metni bir soyut sözdizim ağacına (abstract syntax tree) çevirir, ağaçtaki çağrı düğümlerini gezer ve sabit argümanları imzayla karşılaştırır. İkincisi kodu hiç çalıştırmaz; bu, statik sözcüğünün ölçülebilir karşılığıdır.
Denetleyicinin Elindeki Bilgi
Denetleyici üç şeyi görür: imzadaki açıklamaları, çağrı yerlerini ve çağrılara yazılmış sabit değerleri. Görmediği tek bir şey vardır ve bütün ders o şeyin sonuçlarıdır: değerin anlamını görmez.
yas: int açıklaması “buraya bir tam sayı gelir” der. -5 bir tam sayıdır. Denetleyici
isinstance(-5, int) sınamasını yapar, doğru yanıtını alır ve geçer. Aynı biçimde
ad: str açıklaması boş dizgiyi dışlamaz, çünkü boş dizgi bir dizgidir. Bu bir eksiklik
değil, imzada yazanın tam karşılığıdır: imza tipi bildirir, alanı bildirmez.
Ölçümün varsayımları:
- TP18 — Kâhin kurgunun kendisidir: beş çağrının hangisinin tip, hangisinin alan ihlali taşıdığı ilk derste belirlendi ve burada değiştirilmedi.
- TP19 — Denetleyici ders içinde modellenmiştir; hiçbir dış araç çalıştırılmaz. Modelin kuralı tektir: açıklamadaki tiple bağlanan değerin tipi uyuşmuyorsa bildir.
- TP20 — Alan kuralı da ders içinde modellenmiştir ve yalnız iki koşula bakar: ad boş mu, yaş negatif mi. Bu iki koşul kurgunun alan ihlali tanımıdır.
- TP21 — Kapsam ölçümünde denetlenen kod bir metin sabiti olarak durur; hem soyut sözdizim ağacına çevrilerek denetlenir hem de çalıştırılır. İki katman aynı metne uygulanır.
- TP22 — “Çalıştırılan çağrı yeri”, koşum sırasında gerçekten uğranan çağrı sayısıdır ve izlenen bir sarmalayıcıyla sayılır. Koşum tek bir kiple yapılır.
- TP23 — Soyut sözdizim ağacı denetimi yalnız sabit argümanları sınar; değişkenle verilen argüman bu modelde denetlenmez ve sayılmaz.
- TP24 — Alan tipi ölçümünde
Yassınıfı tam sayıdan türetilir ve kuralı kurulum anında sınar; sınıfın örneği her yerde bir tam sayı gibi davranır. - TP25 — “Ham çağrıda bildirilen”, kaynağa yazıldığı hâliyle beş çağrıdan kaçının bildirildiğidir; sayılan çağrıdır, parametre değil.
- TP26 — “Kurulum sırasında hata veren”, değerin
Yasüzerinden kurulmaya çalışıldığında istisna fırlatan çağrı sayısıdır. - TP27 — Ortama bağlı hiçbir veri yazılmaz; satır numaraları denetlenen metin sabitinin kendi satırlarıdır.
Ölçüm
"""Statik tip denetimi: modellenmis denetleyici neyi bulur, neyi goremez.""" import ast def yas_yaz(ad: str, yas: int) -> str: return f"{ad}:{yas}" CAGRILAR = ( ("ad", 30), # uygun ("ad", "otuz"), # tip ihlali: yas dizgi (7, 30), # tip ihlali: ad sayi ("ad", -5), # tip uygun, alan ihlali: yas negatif ("", 30), # tip uygun, alan ihlali: ad bos ) def denetleyici(islev, cagrilar): """Aciklamalari okuyup cagrilari denetleyen modellenmis denetleyici. Yalnizca TIP uyusmazligini gorur; degerin anlamini goremez. """ aciklama = [(a, t) for a, t in islev.__annotations__.items() if a != "return"] bulunan = [] for i, arg in enumerate(cagrilar): for (ad, tip), deger in zip(aciklama, arg): if not isinstance(deger, tip): bulunan.append((i, ad)) return bulunan def alan_kurali(ad, yas): """Tipin goremedigi anlam kosullari.""" hatalar = [] if isinstance(ad, str) and not ad: hatalar.append("ad bos") if isinstance(yas, int) and yas < 0: hatalar.append("yas negatif") return hatalar bulunan = denetleyici(yas_yaz, CAGRILAR) tip_ihlali = [i for i, a in enumerate(CAGRILAR) if not isinstance(a[0], str) or not isinstance(a[1], int)] alan_ihlali = [i for i, a in enumerate(CAGRILAR) if alan_kurali(*a)] print(f"{'çağrı':>6s} {'tip ihlali':>11s} {'alan ihlali':>12s} " f"{'denetleyici buldu':>18s} {'çalışma zamanı':>15s}") for i, arg in enumerate(CAGRILAR): print(f"{i:6d} {str(i in tip_ihlali):>11s} {str(i in alan_ihlali):>12s} " f"{str(any(b[0] == i for b in bulunan)):>18s} {'hata yok':>15s}") print(f"çağrı {len(CAGRILAR)}, tip ihlali {len(tip_ihlali)}, alan ihlali " f"{len(alan_ihlali)}, denetleyicinin bulduğu {len({b[0] for b in bulunan})}") print(f"denetleyicinin gösterdiği parametreler: {bulunan}") KAYNAK = ''' def dagit(kip): if kip == "kisa": return yas_yaz("ad", 30) if kip == "uzun": return yas_yaz("ad", "otuz") return yas_yaz(7, 30) ''' def statik_denetle(kaynak, islev): """Kaynak metni okuyup cagrilari denetleyen modellenmis denetleyici. Kodu calistirmaz; yalnizca soz dizim agacini gezer. """ imza = [(a, t) for a, t in islev.__annotations__.items() if a != "return"] yerler, bulunan = [], [] for dugum in ast.walk(ast.parse(kaynak)): if isinstance(dugum, ast.Call) and getattr(dugum.func, "id", "") == "yas_yaz": yerler.append(dugum.lineno) for arg, (ad, tip) in zip(dugum.args, imza): if isinstance(arg, ast.Constant) and not isinstance(arg.value, tip): bulunan.append((dugum.lineno, ad)) return yerler, bulunan GECILEN = [] def izlenen(ad, yas): GECILEN.append((ad, yas)) return yas_yaz(ad, yas) ortam = {"yas_yaz": izlenen} exec(KAYNAK, ortam) hata = 0 try: ortam["dagit"]("kisa") except Exception: hata += 1 yerler, statik = statik_denetle(KAYNAK, yas_yaz) print() print(f"kaynaktaki çağrı yeri {len(yerler)}, çalıştırılan {len(GECILEN)}, " f"çalışma zamanında hata {hata}") print(f"denetleyicinin gördüğü çağrı yeri {len(yerler)}, bulduğu ihlal " f"{len({y for y, _ in statik})}, bulduğu yerler {statik}") KAYNAK2 = ''' def yolla(deger): return yas_yaz("ad", deger) ''' yerler2, statik2 = statik_denetle(KAYNAK2, yas_yaz) print(f"değişkenle yazılan çağrı: görülen yer {len(yerler2)}, " f"sınanan argüman 0, bildirilen {len(statik2)}") class Yas(int): """Alan kuralini tipe tasiyan deger nesnesi: kurulum sinar.""" def __new__(cls, deger): if deger < 0: raise ValueError("yas negatif") return super().__new__(cls, deger) def yas_yaz_alan(ad: str, yas: Yas) -> str: return f"{ad}:{yas}" ham = len({b[0] for b in denetleyici(yas_yaz_alan, CAGRILAR)}) kurulan, kurulum_hatasi = [], 0 for a, y in CAGRILAR: try: kurulan.append((a, Yas(y))) except (ValueError, TypeError): kurulum_hatasi += 1 kurulu = len({b[0] for b in denetleyici(yas_yaz_alan, kurulan)}) print() print(f"alan tipli imza: ham çağrıda bildirilen {ham}, kurulum sırasında hata veren " f"{kurulum_hatasi}, kurulan {len(kurulan)} çağrıda bildirilen {kurulu}") print(f"Yas(30) bir tam sayı mı: {isinstance(Yas(30), int)}; " f"boş ad hâlâ görünmüyor: {denetleyici(yas_yaz_alan, [('', Yas(30))])}")
çağrı tip ihlali alan ihlali denetleyici buldu çalışma zamanı
0 False False False hata yok
1 True False True hata yok
2 True False True hata yok
3 False True False hata yok
4 False True False hata yok
çağrı 5, tip ihlali 2, alan ihlali 2, denetleyicinin bulduğu 2
denetleyicinin gösterdiği parametreler: [(1, 'yas'), (2, 'ad')]
kaynaktaki çağrı yeri 3, çalıştırılan 1, çalışma zamanında hata 0
denetleyicinin gördüğü çağrı yeri 3, bulduğu ihlal 2, bulduğu yerler [(7, 'ad'), (6, 'yas')]
değişkenle yazılan çağrı: görülen yer 1, sınanan argüman 0, bildirilen 0
alan tipli imza: ham çağrıda bildirilen 5, kurulum sırasında hata veren 2, kurulan 3 çağrıda bildirilen 1
Yas(30) bir tam sayı mı: True; boş ad hâlâ görünmüyor: []
İki Bulunan, İki Bulunamayan
Üst tablo dersin bağlayıcı sayısını veriyor. Beş çağrının 2’si tip ihlali, 2’si alan
ihlali taşıyor. Denetleyicinin bulduğu çağrı sayısı 2 ve bulduğu çağrılar tam olarak tip
ihlali taşıyanlar: birinci çağrıda yas, ikinci çağrıda ad. Alan ihlali taşıyan iki
çağrıda sütun yanlış diyor.
Kim yakaladı: bu ölçümde iki ihlali denetleyici yakaladı, iki ihlali hiçbir katman yakalamadı. Çalışma zamanı sütunu beş satırda da temiz.
Bulunamayanların nedeni tek satırda okunabilir. Üçüncü çağrıda yaş -5’tir ve -5 bir
tam sayıdır; isinstance(-5, int) doğru döner. Dördüncü çağrıda ad boş dizgidir ve boş
dizgi bir dizgidir; isinstance("", str) doğru döner. Denetleyici yanlış çalışmadı —
sorulan soruyu doğru yanıtladı. Sorulmayan soru “bu değer geçerli mi” sorusuydu ve imzada
o soru yazmıyor.
Buradan kursun ölçü ekseninin en keskin cümlesi çıkıyor: tipin doğru olması değerin
geçerli olduğu anlamına gelmez. Denetleyiciyi daha titiz yazmak bu iki ihlali bulmaz,
çünkü sorun titizlikte değil bilgide: denetleyicinin elinde yas alanının negatif
olamayacağını söyleyen hiçbir kayıt yok.
Denetleyicinin gösterdiği parametre listesi bu bilgi sınırının biçimini de veriyor.
Bildirim yalnız çağrı numarasını değil, hangi parametrenin uymadığını da taşıyor:
birinci çağrıda yas, ikinci çağrıda ad. Bildirim bu kadar somut olabiliyor, çünkü
imzada o parametrenin karşısında bir tip adı yazılı. Alan kuralının karşısında yazılı bir
şey olmadığı için üretilebilecek bir bildirim de yok.
Kapsam Çalıştırılan Kod Değil, Yazılan Koddur
İkinci ölçüm denetleyicinin karşılığında ne verdiğini gösteriyor ve sayı çarpıcı.
Denetlenen metinde yas_yaz için 3 çağrı yeri var; ikisi ihlalli. Program tek bir kiple
çalıştırıldığında bu üç yerden yalnız 1’ine uğranıyor, çünkü öbür ikisi sağlanmayan
koşulların içinde. Çalışma zamanında hata sayısı 0 — hem uğranan çağrı temiz olduğu
için, hem de önceki derste ölçüldüğü gibi açıklama zaten çağrıyı durdurmadığı için.
Soyut sözdizim ağacını gezen denetleyici ise 3 çağrı yerinin üçünü de görüyor ve 2
ihlal bildiriyor; satır numaralarıyla birlikte, hangi parametrenin uymadığını da
söyleyerek. Kodu hiç çalıştırmadı: ast.parse metni bir ağaca çevirdi, ast.walk ağacı gezdi, hiçbir gövde
yürütülmedi.
Aradaki fark bir yüzde değil, bir cins farkı. Çalışma zamanı ancak uğradığı yeri inceler; uğrama koşullara, girdiye ve o koşumdaki kipe bağlıdır. Denetleyicinin kapsamı ise metnin kendisidir: bir kez yazılmış her çağrı yeri, hiç çalıştırılmasa bile denetlenir. Ölçümdeki oran 3/3’e karşı 1/3’tür.
Oranın anlamı koşuma göre değişir. Aynı program üç ayrı kiple çalıştırılsaydı üç çağrı yerinin üçüne de uğranır ve ikinci ile üçüncü çağrılar yine hata vermezdi — çünkü açıklama çağrıyı durdurmaz. Yani 1/3 sayısı denetimin değil, kapsamın ölçüsüdür: hangi kod parçasına bakıldığını söyler, bakılanın doğru bulunup bulunmadığını değil.
Bu, denetleyicinin bulamadığı iki ihlalin bedelini karşılamaz ama onunla takas edilir. Denetleyici daha az şey bilir ve o azı daha çok yerde bilir; çalışma zamanı daha çok şey bilir — elindeki gerçek değeri görür — ve onu daha az yerde bilir.
Modelin Kendi Sınırı
Bir satır bu tablonun altında duruyor ve dürüstlük gereği ayrı sayılmalı. Argümanı bir sabit değil bir değişken olan çağrıda denetleyici çağrı yerini görüyor — 1 — ama sınadığı argüman 0 ve bildirdiği ihlal 0.
Bu, önceki bölümdeki sınırla aynı cinsten değil. Alan ihlalini bulamamak bir bilgi sınırıydı: kural hiçbir yerde yazılı olmadığı için hiçbir denetleyici onu okuyamaz. Değişkenli çağrıyı sınayamamak ise bir model sınırıdır: değişkenin hangi değeri taşıdığı, ona yapılan atamalar izlenerek çıkarılabilir. Bu derste modellenen denetleyici o izlemeyi yapmaz ve sınırı burada açıkça yazılır.
Ayrım önemli, çünkü ikisinin çaresi ayrı. Model sınırı daha iyi bir denetleyiciyle kapanır. Bilgi sınırı kapanmaz — kuralı bir yere yazmadan hiçbir denetleyici onu bulamaz. Bir sonraki bölüm o “bir yer”in ne olabileceğini deniyor.
Alan Kuralını Tipe Taşımak
Üçüncü ölçüm doğal bir soruyu deniyor: denetleyicinin elinde alan bilgisi yoksa, o bilgiyi
imzaya koysak ne olur? Ölçüm tam sayıdan türeyen bir Yas sınıfı kuruyor; sınıf
kuralını kurulum anında sınıyor ve örneği her yerde bir tam sayı gibi davranıyor — son satır
bunu isinstance ile gösteriyor.
İmza yas: Yas olduğunda denetleyici gerçekten değişiyor. Kaynağa yazıldığı hâliyle beş
çağrının 5’i bildiriliyor: hiçbir sabit sayı bir Yas örneği değildir, temiz çağrı bile
değildir. Yani alan bilgisini imzaya koymak, her çağrı yerini değeri kurmaya zorluyor.
Değerler kurulduğunda tablo yerine oturuyor. Beş çağrının 2’si kurulum sırasında hata
veriyor — negatif yaş ve tam sayı olmayan yaş, ikisi de Yas yapıcısında düşüyor. Geriye
kalan 3 çağrıdan 1’i denetleyici tarafından bildiriliyor: adı sayı olan çağrı.
Ama son satır kalan boşluğu gösteriyor: boş ad hâlâ görünmüyor, çünkü ad için böyle bir
tip kurulmadı. Dört ihlalden üçü artık yakalanmış durumda ve yakalayanlar ayrı katmanlar —
1’ini denetleyici, 2’sini kurulum.
Buradan çıkan sonuç dersin sonucudur ve bir sonrakinin başlangıcı. Alan kuralını tipe
taşımak ihlali görünür kılar, ama görünür kıldığı yer denetleyici değil çalışma
zamanıdır: Yas(-5) çağrısı bir istisna fırlatır ve bu istisna program çalışırken çıkar.
Kural yazıya değil, bir yapıcının gövdesine taşınmıştır.
Özet
- Statik tip denetimi, kodu çalıştırmadan okuyup çağrıları imzayla karşılaştıran bir katmandır; bu derste denetleyici ders içinde modellenmiştir ve hiçbir dış araç kullanılmamıştır.
- Denetleyici beş çağrıda 2 tip ihlalini bulur ve 2 alan ihlalini göremez. Nedeni
bilgi eksikliğidir:
-5bir tam sayıdır, boş dizgi bir dizgidir ve imzada alan kuralı yazmaz. - Denetimin kapsamı yazılan koddur: soyut sözdizim ağacını gezen denetleyici 3 çağrı yerinin üçünü de görüp 2 ihlal bildirir, oysa koşum yalnız 1 çağrı yerine uğrar ve 0 hata verir.
- Denetleyici daha az şeyi daha çok yerde, çalışma zamanı daha çok şeyi daha az yerde bilir; ikisi birbirinin yerine geçmez.
- İki sınır ayrı cinstendir: değişkenli çağrıyı sınayamamak bir model sınırıdır ve daha iyi bir denetleyiciyle kapanır; alan kuralını görememek bir bilgi sınırıdır ve kural bir yere yazılmadan kapanmaz.
- Alan kuralı tipe taşındığında ham çağrıların 5’i bildirilir, 2’si kurulum sırasında hata verir, kurulan üç çağrıdan 1’i denetleyiciye takılır — kural yazıdan bir yapıcının gövdesine, yani çalışma zamanına taşınmıştır.
Sonraki Adım
Üçüncü ölçüm kuralı çalışma zamanına taşıdı ama bunu tek bir alan için, elle yazılmış bir sınıfla yaptı. Her alan için ayrı bir sınıf yazmak ölçeklenmez; kural sayısı arttıkça yapıcılar birbirini tekrar eder ve kuralların nerede durduğu dağılır. Kuralları tek bir yerde, verinin şeması olarak toplamak ve bu şemayı çağrı anında uygulamak da mümkündür. Sonraki ders bu katmanı ders içinde modelliyor ve kursun son sayısını soruyor: çalışma zamanı, denetleyici ve alan kuralı ayrı ayrı kaç ihlal yakalar — ve dört ihlalin tamamını görmek için kaç katman gerekir?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.