Ders 08 / 22
Atom Tabanlı Yaklaşımlar
İnce taneli tepkisel durum; atom ve türetilmiş atom, bağımlılık çizgesi, kirletme yayılımı ile tembel hesaplama, yeniden hesap ve uyarı sayısının merkezî depoyla karşılaştırılması, çizge modelinin bedeli.
İçindekiler
Merkezî depo tek bir doğru kaynak kurdu, ama bir bedeli çıktı: her değişiklik tek bir durum nesnesini yeniler ve tüm dinleyicilere duyurulur. Gereksiz çizimi seçiciler önler — yani doğruluk, her tüketicinin kendi süzgecini kusursuz kurmasına bağlıdır. Bir seçici unutulduğunda ya da her çağrıda yeni nesne ürettiğinde kusur sessizdir; ekran doğru görünür, yalnız gereğinden çok çizilir.
Sorumluluğu ters çeviren bir model daha vardır. Durum tek bir büyük nesne olarak değil, birbirine bağlı küçük birimler olarak tanımlanır. O zaman kimin neyi okuduğu bağımlılıktan kendiliğinden çıkar ve süzme işi tüketiciden çizgeye geçer.
Atom ve Türetilmiş Atom
Atom, bağımsız olarak okunup yazılabilen en küçük durum birimidir. Kuzey Yamaç Ölçüm İstasyonu uygulamasında ölçüm listesi bir atom, süzgeç ölçütü bir atom, sıralama yönü bir atom, arayüz teması bir atomdur. Aralarında kapsayan bir nesne yoktur; her biri kendi başına durur.
Türetilmiş atom ise yazılamaz; girdilerinden hesaplanır. Süzülmüş liste, sıralanmış liste ve özet birer türetilmiş atomdur. Durum Türleri dersindeki kural burada bir birim olarak somutlaşır: türetilmiş değer saklanmaz, tanımlanır.
İkisi birlikte bir bağımlılık çizgesi kurar. Kenarlar okuma yönünde çizilir: süzülmüş liste, ölçüm listesini ve süzgeci okur. Görünümler de bu çizgenin yaprakları gibi davranır; okudukları düğümlere bağlanırlar.
Çizgenin işleyişi iki adımlıdır. Bir atom yazıldığında ondan erişilen tüm türetilmiş düğümler kirletilir — değerleri artık geçerli sayılmaz. Kirlenmiş bir düğümün değeri okunduğunda yeniden hesaplanır. Yazma anında hesap yapılmaz.
// atom-cizgesi.mjs — atomlar, turetilmis atomlar, kirletme yayilimi ve yeniden hesap sayimi const hesaplananlar = []; function atom(ad, deger) { return { ad, tur: "atom", deger, bagimli: new Set() }; } function turetilmis(ad, girdiler, hesap) { const d = { ad, tur: "turetilmis", girdiler, hesap, kirli: true, deger: undefined, bagimli: new Set() }; for (const g of girdiler) g.bagimli.add(d); return d; } function tuketici(ad, girdiler) { const t = { ad, tur: "tuketici", girdiler, bagimli: new Set() }; for (const g of girdiler) g.bagimli.add(t); return t; } function oku(d) { if (d.tur === "atom") return d.deger; if (d.kirli) { d.deger = d.hesap(...d.girdiler.map(oku)); d.kirli = false; hesaplananlar.push(d.ad); } return d.deger; } function yaz(a, deger, etkin) { hesaplananlar.length = 0; a.deger = deger; const uyarilan = new Set(); const kirlet = (d) => { for (const b of d.bagimli) { if (b.tur === "tuketici") { uyarilan.add(b.ad); continue; } if (b.kirli) continue; // zaten kirli, yayilim durur b.kirli = true; kirlet(b); } }; kirlet(a); const cizilen = etkin.filter((t) => uyarilan.has(t.ad)); // ekranda olmayan uyarilmaz for (const t of cizilen) t.girdiler.forEach(oku); return { uyarilan: cizilen.map((t) => t.ad), hesaplanan: [...hesaplananlar] }; } const olcumler = atom("olcumler", [ { tur: "sicaklik", d: -4.2 }, { tur: "nem", d: 71 }, { tur: "sicaklik", d: -3.8 }]); const suzgec = atom("suzgec", "hepsi"); const sira = atom("sira", "azalan"); const tema = atom("tema", "acik"); const suzulmus = turetilmis("suzulmus", [olcumler, suzgec], (o, s) => s === "hepsi" ? o : o.filter((x) => x.tur === s)); const siralanmis = turetilmis("siralanmis", [suzulmus, sira], (o, s) => [...o].sort((a, b) => s === "artan" ? a.d - b.d : b.d - a.d)); const ozet = turetilmis("ozet", [suzulmus], (o) => ({ sayi: o.length, ortalama: o.length ? o.reduce((t, x) => t + x.d, 0) / o.length : null })); const basliklar = turetilmis("basliklar", [tema], (t) => ({ arkaPlan: t === "koyu" ? "#111" : "#fff" })); const Liste = tuketici("Liste", [siralanmis]); const OzetKarti = tuketici("OzetKarti", [ozet]); const Baslik = tuketici("Baslik", [basliklar]); let etkin = [Liste, OzetKarti, Baslik]; etkin.forEach((t) => t.girdiler.forEach(oku)); // ilk cizim console.log("-- yazma basina uyarilan tuketici ve yeniden hesap --"); const adimlar = [ ["tema = koyu", () => yaz(tema, "koyu", etkin)], ["sira = artan", () => yaz(sira, "artan", etkin)], ["suzgec = sicaklik", () => yaz(suzgec, "sicaklik", etkin)], ["olcumler += kayit", () => yaz(olcumler, [...olcumler.deger, { tur: "sicaklik", d: -5.1 }], etkin)], ]; let toplamUyari = 0, toplamHesap = 0; for (const [etiket, calistir] of adimlar) { const { uyarilan, hesaplanan } = calistir(); toplamUyari += uyarilan.length; toplamHesap += hesaplanan.length; console.log(etiket.padEnd(20), "uyarilan:", (uyarilan.join(", ") || "-").padEnd(26), "hesaplanan:", hesaplanan.join(", ") || "-"); } console.log("-- OzetKarti ekrandan kaldirildi --"); etkin = [Liste, Baslik]; const s = yaz(suzgec, "nem", etkin); toplamUyari += s.uyarilan.length; toplamHesap += s.hesaplanan.length; console.log("suzgec = nem".padEnd(20), "uyarilan:", (s.uyarilan.join(", ") || "-").padEnd(26), "hesaplanan:", s.hesaplanan.join(", ") || "-"); console.log("ozet kirli mi:", ozet.kirli); console.log("-- toplam --"); console.log("ince taneli : uyari", toplamUyari, "| yeniden hesap", toplamHesap); console.log("tek nesne : uyari", 5 * 3, "| (her yazmada her tuketici)");
-- yazma basina uyarilan tuketici ve yeniden hesap -- tema = koyu uyarilan: Baslik hesaplanan: basliklar sira = artan uyarilan: Liste hesaplanan: siralanmis suzgec = sicaklik uyarilan: Liste, OzetKarti hesaplanan: suzulmus, siralanmis, ozet olcumler += kayit uyarilan: Liste, OzetKarti hesaplanan: suzulmus, siralanmis, ozet -- OzetKarti ekrandan kaldirildi -- suzgec = nem uyarilan: Liste hesaplanan: suzulmus, siralanmis ozet kirli mi: true -- toplam -- ince taneli : uyari 7 | yeniden hesap 10 tek nesne : uyari 15 | (her yazmada her tuketici)
Her satır tek bir yazmanın etkisini gösterir. Tema değiştiğinde yalnız başlık düğümü yeniden hesaplanır; ölçüm listesine dokunulmaz. Sıralama yönü değiştiğinde süzülmüş liste yerinde kalır, yalnız sıralama yeniden yapılır — süzme işi tekrarlanmaz. Süzgeç değiştiğinde ise üç düğüm birden kirlenir, çünkü ikisi süzülmüş listeye bağlıdır.
Yayılımın bir ayrıntısı koda yazılıdır: zaten kirli olan düğümde yayılım durur. Aynı yazma sırasında bir düğüme iki yoldan ulaşılsa bile bir kez işaretlenir ve bir kez hesaplanır.
Tembel Hesaplama
Özet kartı ekrandan kaldırıldıktan sonra yapılan yazma modelin ikinci kazancını gösterir. Süzülmüş liste ve sıralanmış liste yeniden hesaplanır, çünkü liste hâlâ ekrandadır. Özet düğümü ise yalnız kirletilmiş, hesaplanmamıştır; çıktının son satırı bunu doğrular.
Bunun nedeni hesabın yazmada değil okumada yapılmasıdır. Kimsenin okumadığı bir türetilmiş değer hiç hesaplanmaz. Özet kartı yeniden ekrana geldiğinde okuma gerçekleşir ve değer o anda üretilir.
Bu davranış Programlama Temelleri kursundaki tembel değerlendirmenin tepkisel karşılığıdır ve pahalı türetmelerde tek başına belirleyici olabilir: bin satırlık bir tabloyu gruplayan bir hesap, o tablo görünmediği sürece hiç çalışmaz.
Kaba Taneli ile Karşılaştırma
Son iki satır iki modeli aynı senaryoda karşılaştırır. Beş yazmada ince taneli çizge yedi tüketici uyarısı üretmiştir; tek nesneli depo, seçici kullanılmadığında on beş üretirdi.
Fark, seçicilerle kapatılabilir bir farktır — ama kapatılması gereken bir farktır. Atom modelinde süzgeç, bağımlılığın kendisinden çıkar: bir tüketici neyi okuduysa ona bağlıdır. Merkezî depoda ise süzgeç elle yazılır ve yanlış yazıldığında kimse uyarmaz.
Karşılığında kaybedilen şey de vardır. Merkezî depoda tüm değişimler tek bir eylem akışından geçer; sıralanabilir, kaydedilebilir, yeniden oynatılabilir. Atom çizgesinde yazma doğrudan atoma yapılır; “bu değeri kim değiştirdi” sorusunun tek bir yanıt noktası yoktur. Eylem günlüğü ve yeniden oynatma bu modelde kendiliğinden gelmez.
Çizgenin Bedeli
İnce tanelilik üç yeni sorun getirir.
Dağınıklık. Durum tanımı tek bir yerde toplanmaz; atomlar kullanıldıkları yerlerin yanına dağılır. Küçük uygulamada bu bir kazançtır. Büyüdükçe, bir atomun kim tarafından yazıldığını bulmak dosya taramaya dönüşür. Karşı önlem, yazma işlemlerini adlandırılmış işlevlerin arkasına almak ve atomu doğrudan yazılabilir bırakmamaktır.
Bağımlılık döngüsü. Bir türetilmiş atomun dolaylı olarak kendini okuması, kirletme yayılımını sonsuz döngüye sokar. Örnekteki kirletme yayılımı buna karşı kısmen korunur — zaten kirli düğümde durur — ama okuma tarafı sonsuz özyinelemeye girer. Çizge, kurulurken çevrimsizlik açısından denetlenmelidir; Veri Yapıları kursundaki yönlü çevrimsiz çizge kavramı burada bir kısıttır.
Kimlik. Liste ögesi başına atom gerektiğinde — her ölçüm satırının kendi seçili durumu gibi — atomlar bir aile oluşturur ve anahtarla üretilir. Bu ailenin temizlenmesi elle yapılır; ekrandan kalkan ögenin atomu bırakılmazsa bellek sızıntısı oluşur. Eşzamansız JavaScript ve Çalışma Zamanı kursundaki bellek sızıntısı tanısı bu durumda doğrudan uygulanır.
Hangi Model Nerede
İki model birbirinin alternatifi değil, farklı ölçütlere göre seçilen araçlardır.
Merkezî depo, değişimin izlenebilirliği birinci öncelikse tercih edilir: karmaşık iş kuralları, geri alma gereksinimi, çok adımlı akışlar, hata bildirimiyle birlikte alınan eylem günlüğü.
Atom modeli, granülerlik birinci öncelikse tercih edilir: birbirinden bağımsız çok sayıda küçük durum, pahalı türetmeler, sık güncellenen ve az sayıda tüketicisi olan değerler.
İkisi aynı uygulamada birlikte de bulunabilir. Ölçüt kalıbın kendisi değil, o durumun hangi soruyu doğurduğudur.
Özet
- Atom bağımsız okunup yazılabilen en küçük durum birimidir; türetilmiş atom yazılamaz, girdilerinden hesaplanır ve saklanmaz.
- Yazma, çizgede erişilen düğümleri kirletir; hesap yazmada değil okumada yapılır, böylece kimsenin okumadığı türetilmiş değer hiç hesaplanmaz.
- Kirletme yayılımı zaten kirli düğümde durduğu için bir yazmada her düğüm en çok bir kez hesaplanır.
- İnce taneli çizgede süzme işi bağımlılığın kendisinden çıkar; merkezî depoda aynı sonuç elle yazılan seçicilerle elde edilir ve yanlış yazıldığında sessizce kaybolur.
- Atom modelinde tek bir eylem akışı olmadığı için eylem günlüğü, yeniden oynatma ve geri alma kendiliğinden gelmez.
- Model seçimi ölçüte bağlıdır: izlenebilirlik merkezî depoyu, granülerlik atom çizgesini öne çıkarır; ikisi bir arada da kullanılabilir.
Sonraki Adım
Bu iki modelin ortak bir varsayımı var: durumun sahibi uygulamadır, yazılan değer doğrudur. Kuzey Yamaç Ölçüm İstasyonu uygulamasının en büyük durum kütlesi ise bu varsayımı karşılamıyor. İstasyon listesi ve ölçüm kayıtları uzaktaki bir kaynağın kopyası; başka bir kullanıcı yeni istasyon eklemiş, biri bir ölçümü düzeltmiş olabilir. Bu durumun sorusu “nerede tutulacağı” değil, “ne kadar süre güncel sayılacağı”. Sonraki ders sunucu durumunu kendi katmanında ele alır: önbellek anahtarı, tazelik penceresi, aynı veriyi isteyen iki bileşenin tek isteğe indirgenmesi, geçersizleştirme ve yeniden getirme.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.