İçeriğe geç
academia.sh

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 Yas sı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: -5 bir 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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat