İçeriğe geç
academia.sh

Ders 06 / 18

Kara Tahta Mimarisi

Birbirini tanımayan çözümleyicilerin ortak bir bilgi alanı üzerinden buluşması: bir adresten ücret bölgesinin belirlenmesinde birimlerin birbiri hakkında bildiği ad sayısının sıfır çıkması, aynı sonuca ulaşmak için harcanan fazladan okuma ve yoklama sayısı, yeni bir çözümleyicinin düzenlettiği dosya sayısı ve yirmi dört yoklama sırasında sonucun belirlenimci olup olmadığı.

İçindekiler

Bir önceki dersin çekirdeği eklentilerin adını bilmiyordu, ama sıranın ne olduğunu ve sonucun nasıl toplanacağını biliyordu.

Bu ders o merkezi de kaldırır. Birimler ne birbirini ne kendilerini çağıran bir düzeni tanır; ortak bir bilgi alanına yazar, oradan okur ve buluşmayı alanın kendisi sağlar. Üç şey ölçülür: bildikleri ad sayısı, aynı sonuç için harcanan fazladan iş — kaç birim aynı veriyi okudu, kaç tur boşa döndü — ve aynı girdinin her koşumda aynı çıktıyı verip vermediği.

Kara Tahta, Çözümleyici ve Denetim

Kara tahta (blackboard) düzeni üç parçadan oluşur. Birincisi tahtadır: adlandırılmış kısmi sonuçların ortak alanı. İkincisi çözümleyicilerdir: tahtadan okuyup tahtaya yazan, birbirinin adını bilmeyen birimler. Üçüncüsü denetim döngüsüdür: hangi çözümleyicinin ne zaman yoklanacağına karar veren ama ne yaptıklarını bilmeyen döngü.

Bu, gözlemci kalıbındaki bildirimden farklıdır: Tasarım Kalıpları kursundaki Gözlemci dersinde yayıncı abone listesini tutar ve bildirimi doğrudan onlara gönderir, oysa kara tahtada yazan taraf kimin okuyacağını bilmez. Ayırt edici kısıt şudur: bir çözümleyici yalnız tahtayı okuyup yazabilir, başka bir çözümleyiciyi çağıramaz.

Belirsiz Bölge ve Dört Çözümleyici

Ücretin bağlı olduğu bölge, gelen adresten tek başına belirlenemiyor: posta kodunda bir hane eksik. Dört çözümleyici aynı sözleşmeyi karşılıyor — ad ve coz(oku) — ve her biri kısmi bir sonuçla bir güven derecesi döndürür. İl adı çözümleyicisi bölge iddiası yazmaz, yalnız normalize edilmiş il adını yazar; posta kodu çözümleyicisi kod eksikse o ada gerek duyar.

mkdir -p kara-tahta/cozumleyiciler
cat > kara-tahta/ucret.mjs <<'EOF'
// kara-tahta/ucret.mjs — bolgeden ucrete: ayni gonderi, bolgeye gore katsayi
const TABAN = 8490;
const ADIM = 50;
const KATSAYI = { yakin: 1, orta: 1.35, uzak: 1.8 };

export const tutar = (bolge) => Math.round((TABAN * (KATSAYI[bolge] ?? KATSAYI.uzak)) / ADIM) * ADIM;
EOF
cat > kara-tahta/cozumleyiciler/il-adi.mjs <<'EOF'
// kara-tahta/cozumleyiciler/il-adi.mjs — il adini normalize eder; bolge iddiasi yazmaz
const ILLER = ["istanbul", "ankara", "izmir", "van"];

export const ilAdi = { ad: "il-adi", coz: (oku) => {
  const ad = oku("adres").il.toLowerCase();
  return ILLER.includes(ad) ? { guven: 0.7, yaz: { ilNormal: ad } } : null;
} };
EOF
cat > kara-tahta/cozumleyiciler/posta-kodu.mjs <<'EOF'
// kara-tahta/cozumleyiciler/posta-kodu.mjs — tam kod dogrudan; eksik kod il icindeki en uzak bolgeye duser
const ONEK = { 34: "yakin", "06": "orta", 35: "orta", 65: "uzak" };
const IL_EN_UZAK = { istanbul: "orta", ankara: "uzak", izmir: "uzak", van: "uzak" };

export const postaKodu = { ad: "posta-kodu", coz: (oku) => {
  const kod = oku("adres").postaKodu;
  if (kod.length === 5) return { guven: 0.85, yaz: { bolge: ONEK[kod.slice(0, 2)] ?? "uzak" } };
  const il = oku("ilNormal");
  return il === undefined ? null : { guven: 0.3, yaz: { bolge: IL_EN_UZAK[il] ?? "uzak" } };
} };
EOF
cat > kara-tahta/cozumleyiciler/koordinat.mjs <<'EOF'
// kara-tahta/cozumleyiciler/koordinat.mjs — merkeze kus ucusu yaklasik uzakligi esiklere vurur
const MERKEZ = { enlem: 41.02, boylam: 28.98 };
const ESIK = [[300, "yakin"], [900, "orta"]];

export const koordinat = { ad: "koordinat", coz: (oku) => {
  const a = oku("adres");
  const dEnlem = (a.enlem - MERKEZ.enlem) * 111;
  const dBoylam = (a.boylam - MERKEZ.boylam) * 111 * Math.cos((MERKEZ.enlem * Math.PI) / 180);
  const km = Math.round(Math.hypot(dEnlem, dBoylam));
  return { guven: 0.9, yaz: { bolge: ESIK.find(([s]) => km <= s)?.[1] ?? "uzak" } };
} };
EOF
cat > kara-tahta/cozumleyiciler/gecmis-gonderi.mjs <<'EOF'
// kara-tahta/cozumleyiciler/gecmis-gonderi.mjs — ayni musteriye kesilmis son gonderilerin bolgesi
const GECMIS = { "M-118": "yakin", "M-204": "orta" };

export const gecmisGonderi = { ad: "gecmis-gonderi", coz: (oku) => {
  const b = GECMIS[oku("adres").musteri];
  return b === undefined ? null : { guven: 0.55, yaz: { bolge: b } };
} };
EOF

Tahta, Denetim Döngüsü ve Doğrudan Karşılık

Tahta iki yazma kuralından birini alır: ilk yazan kazanır kuralında dolu bir anahtara ikinci yazma reddedilir, en güvenli kazanır kuralında yalnız daha yüksek güvenli yazma geçer.

// kara-tahta/tahta.mjs — ortak bilgi alani: yazma kurali, okuma ve yazma sayaclari
export const karaTahta = (kural, baslangic) => {
  const alan = new Map(Object.entries(baslangic));
  const guven = new Map();
  const okuyan = new Map();
  const sayac = { okuma: 0, yazma: 0, reddedilen: 0 };
  return {
    oku: (anahtar, kim) => {
      sayac.okuma += 1;
      okuyan.set(anahtar, (okuyan.get(anahtar) ?? new Set()).add(kim));
      return alan.get(anahtar);
    },
    yaz: (anahtar, deger, g) => {
      const dolu = alan.has(anahtar);
      const gecer = kural === "ilk-yazan" ? dolu === false : (guven.get(anahtar) ?? -1) < g;
      if (gecer === false) { sayac.reddedilen += 1; return; }
      alan.set(anahtar, deger);
      guven.set(anahtar, g);
      sayac.yazma += 1;
    },
    deger: (anahtar) => alan.get(anahtar),
    olcum: () => ({ ...sayac, okuyanBirim: Object.fromEntries([...okuyan].map(([a, s]) => [a, s.size])) }),
  };
};

Denetim döngüsü verilen sırayla yoklar: katkı veren çözümleyici bekleyenlerden çıkar, veremeyen sonraki turda yeniden yoklanır ve döngü hiçbir katkının gelmediği bir tur görene kadar sürer.

// kara-tahta/denetim.mjs — denetim dongusu: verilen sirayla yoklar, katki kalmayana kadar tur cevirir
export const denetimDongusu = (t, cozumleyiciler) => {
  const bekleyen = new Set(cozumleyiciler);
  let tur = 0, yoklama = 0, katki = 0, bosTur = 0;
  while (true) {
    tur += 1;
    let turKatkisi = 0;
    for (const c of cozumleyiciler) {
      if (bekleyen.has(c) === false) continue;
      yoklama += 1;
      const sonuc = c.coz((a) => t.oku(a, c.ad));
      if (sonuc === null) continue;
      bekleyen.delete(c);
      for (const [a, d] of Object.entries(sonuc.yaz)) t.yaz(a, d, sonuc.guven);
      katki += 1;
      turKatkisi += 1;
    }
    if (turKatkisi === 0) { bosTur += 1; break; }
  }
  return { tur, yoklama, katki, bosTur };
};

Karşılaştırılacak düzen aynı dört çözümleyiciyi doğrudan çağırır: sıra ve seçim kuralı bu dosyada yazılıdır; ölçüm için sıra parametreleştirildi.

// kara-tahta/dogrudan.mjs — dogrudan cagri: dort adi, sirayi ve secim kuralini bu dosya biliyor
import { ilAdi } from "./cozumleyiciler/il-adi.mjs";
import { postaKodu } from "./cozumleyiciler/posta-kodu.mjs";
import { koordinat } from "./cozumleyiciler/koordinat.mjs";
import { gecmisGonderi } from "./cozumleyiciler/gecmis-gonderi.mjs";

export const SIRA = [ilAdi, postaKodu, koordinat, gecmisGonderi];

export const dogrudanCoz = (adres, sira = SIRA) => {
  const veri = { adres };
  let okuma = 0, katki = 0, enIyi = { guven: -1, bolge: undefined };
  for (const c of sira) {
    const sonuc = c.coz((a) => (okuma += 1, veri[a]));
    if (sonuc === null) continue;
    katki += 1;
    Object.assign(veri, sonuc.yaz);
    const b = sonuc.yaz.bolge;
    if (b !== undefined && sonuc.guven > enIyi.guven) enIyi = { guven: sonuc.guven, bolge: b };
  }
  return { ...enIyi, tur: 1, yoklama: sira.length, katki, okuma };
};
// kara-tahta/kur.mjs — bilesim koku: cozumleyici listesi ve olculen adres
import { postaKodu } from "./cozumleyiciler/posta-kodu.mjs";
import { ilAdi } from "./cozumleyiciler/il-adi.mjs";
import { koordinat } from "./cozumleyiciler/koordinat.mjs";
import { gecmisGonderi } from "./cozumleyiciler/gecmis-gonderi.mjs";

export const COZUMLEYICILER = [postaKodu, ilAdi, koordinat, gecmisGonderi];

export const ADRES = { postaKodu: "0680", il: "ANKARA", enlem: 39.93, boylam: 32.86, musteri: "M-118" };

Bilinen Ad ve Yapılan İş

Betik her dosyada kaç başka birim adının geçtiğini sayar ve iki düzeni koşturur.

// ad-ve-is.mjs — birimlerin birbiri hakkinda bildigi ad sayisi ve iki duzenin yaptigi is
import { readdirSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { karaTahta } from "./kara-tahta/tahta.mjs";
import { denetimDongusu } from "./kara-tahta/denetim.mjs";
import { dogrudanCoz } from "./kara-tahta/dogrudan.mjs";
import { COZUMLEYICILER, ADRES } from "./kara-tahta/kur.mjs";
import { tutar } from "./kara-tahta/ucret.mjs";

const ADLAR = COZUMLEYICILER.map((c) => c.ad);
const dosyalar = readdirSync("kara-tahta", { recursive: true })
  .map((a) => join("kara-tahta", a)).filter((y) => y.endsWith(".mjs")).sort();

const tablo = dosyalar.map((yol) => {
  const metin = readFileSync(yol, "utf8");
  const kendi = ADLAR.find((a) => yol.includes(a));
  return [yol, ADLAR.filter((a) => a !== kendi && metin.includes(a))];
});
console.log(`dosya = ${tablo.length}, baska birim adi 0 olan = ${tablo.filter(([, b]) => b.length === 0).length}`);
for (const [yol, b] of tablo.filter(([, b]) => b.length > 0)) {
  console.log(`  ${yol.padEnd(23)} baska birim adi = ${b.length}  (${b.join(", ")})`);
}

const t = karaTahta("en-guvenli", { adres: ADRES });
const o = denetimDongusu(t, COZUMLEYICILER);
const m = t.olcum();
const b = t.deger("bolge");
console.log(`kara tahta     bolge = ${b} tutar = ${tutar(b)}`
  + `  tur = ${o.tur}  yoklama = ${o.yoklama}  katki = ${o.katki}  bos tur = ${o.bosTur}`);
console.log(`  okuma = ${m.okuma}  yazma = ${m.yazma}  reddedilen yazma = ${m.reddedilen}`
  + `  okuyan birim = ${Object.entries(m.okuyanBirim).map(([a, n]) => `${a}:${n}`).join(" ")}`);

const d = dogrudanCoz(ADRES);
console.log(`dogrudan cagri bolge = ${d.bolge} tutar = ${tutar(d.bolge)}`
  + `  tur = ${d.tur}  yoklama = ${d.yoklama}  katki = ${d.katki}  bos tur = 0`);
console.log(`  okuma = ${d.okuma}  yazma = yok  reddedilen yazma = yok`);
node ad-ve-is.mjs
dosya = 9, baska birim adi 0 olan = 7
  kara-tahta/dogrudan.mjs baska birim adi = 4  (posta-kodu, il-adi, koordinat, gecmis-gonderi)
  kara-tahta/kur.mjs      baska birim adi = 4  (posta-kodu, il-adi, koordinat, gecmis-gonderi)
kara tahta     bolge = orta tutar = 11450  tur = 3  yoklama = 5  katki = 4  bos tur = 1
  okuma = 7  yazma = 2  reddedilen yazma = 2  okuyan birim = adres:4 ilNormal:1
dogrudan cagri bolge = orta tutar = 11450  tur = 1  yoklama = 4  katki = 4  bos tur = 0
  okuma = 5  yazma = yok  reddedilen yazma = yok

Dokuz dosyanın yedisi başka hiçbir birimin adını taşımıyor: tahta, denetim döngüsü, ücret dönüşümü ve dört çözümleyicinin hepsi 0. Adları bilen iki dosya var, ikisi de 4 taşıyor ve fark o dosyaların cinsindedir: kara tahtada bilgi bileşim kökündeki bir listede, doğrudan çağrı düzeninde çağrı sırasının ve seçim kuralının yanında duruyor.

İkinci yarı bedeli gösteriyor. İki düzen de aynı bölgeyi ve aynı tutarı buldu, ama kara tahta 3 tur döndü, 5 yoklama yaptı ve 7 okuma harcadı; doğrudan çağrı 1 turda 4 yoklama ve 5 okumayla bitti. Fazladan yoklama posta kodu çözümleyicisine ait: ilk turda normalize il adı tahtada yoktu ve ikinci turda yeniden yoklandı; bir tur da yalnız sonlanmayı anlamak için boşa döndü. adres anahtarını dört birim okudu, çünkü hiçbiri ötekinin ne okuduğunu bilmiyor.

Aynı Girdi, Yirmi Dört Sıra

Denetim döngüsü sırayı dışarıdan alıyor; sıra değiştiğinde sonucun değişmesi ölçülebilir bir sorudur. Dört çözümleyicinin 24 dizilişi koşturuluyor.

// belirlenim.mjs — ayni girdi, 24 farkli yoklama sirasi: sonuc ve katki siraya bagli mi
import { karaTahta } from "./kara-tahta/tahta.mjs";
import { denetimDongusu } from "./kara-tahta/denetim.mjs";
import { dogrudanCoz } from "./kara-tahta/dogrudan.mjs";
import { COZUMLEYICILER, ADRES } from "./kara-tahta/kur.mjs";
import { tutar } from "./kara-tahta/ucret.mjs";

const permutasyon = (d) => (d.length <= 1 ? [d]
  : d.flatMap((e, i) => permutasyon([...d.slice(0, i), ...d.slice(i + 1)]).map((p) => [e, ...p])));
const SIRALAR = permutasyon(COZUMLEYICILER);

const dagilim = (kosumlar, alan) => {
  const m = new Map();
  for (const k of kosumlar) m.set(k[alan], (m.get(k[alan]) ?? 0) + 1);
  return [...m].sort();
};

const bildir = (ad, kosumlar) => {
  const bolgeler = dagilim(kosumlar, "bolge");
  console.log(`${ad.padEnd(23)} farkli bolge = ${bolgeler.length}:`
    + ` ${bolgeler.map(([b, n]) => `${b} ${n} sira ${tutar(b)}`).join(", ")}`);
  console.log(`  katki = ${dagilim(kosumlar, "katki").map(([k, n]) => `${k} (${n} sira)`).join(", ")}`);
};

for (const kural of ["ilk-yazan", "en-guvenli"]) {
  bildir(`kara tahta / ${kural}`, SIRALAR.map((sira) => {
    const t = karaTahta(kural, { adres: ADRES });
    const o = denetimDongusu(t, sira);
    return { bolge: t.deger("bolge"), katki: o.katki };
  }));
}
bildir("dogrudan cagri", SIRALAR.map((sira) => dogrudanCoz(ADRES, sira)));
node belirlenim.mjs
kara tahta / ilk-yazan  farkli bolge = 3: orta 11 sira 11450, uzak 2 sira 15300, yakin 11 sira 8500
  katki = 4 (24 sira)
kara tahta / en-guvenli farkli bolge = 1: orta 24 sira 11450
  katki = 4 (24 sira)
dogrudan cagri          farkli bolge = 1: orta 24 sira 11450
  katki = 3 (12 sira), 4 (12 sira)

İlk satır çifti düzenin en pahalı kusurunu gösteriyor. İlk yazan kazanır kuralıyla aynı adres 24 sıralamada üç ayrı bölgeye çözümlendi ve aynı gönderi 8.500, 11.450 ya da 15.300 kuruş ücretlendi. Sonucu belirleyen şey adres değil, denetim döngüsünün hangi çözümleyiciyi önce yokladığıdır; bu kural altında düzen belirlenimci değildir.

İkinci çift kusurun tahtada değil yazma kuralında olduğunu gösteriyor: en güvenli kazanır kuralıyla 24 sıralamanın hepsi aynı bölgeyi ve aynı tutarı verdi. Belirlenimciliği sağlayan şey, karşılaştırılabilir bir güven derecesinin sözleşmede olmasıdır.

Üçüncü çift kara tahtanın karşılığını veriyor. Doğrudan çağrı her sırada aynı sonucu üretti, ama 24 sıralamanın 12’sinde katkı 4 yerine 3 çıktı: posta kodu çözümleyicisi normalize il adından önce çağrıldığı için elenip bir daha sorulmadı. Kara tahtada katkı hepsinde 4, çünkü bekleyen çözümleyici sonraki turda yeniden yoklanıyor.

Beşinci Çözümleyici

Beşinci çözümleyici müşteri sözleşmesinde yazılı varsayılan bölgeyi öneriyor. Blok, ağacın bir kopyasına onu ekler, düzenlenen dosyaları listeler ve yamalı ağacı yükler.

cp -r kara-tahta besinci
cat > besinci/cozumleyiciler/sozlesme-varsayilani.mjs <<'EOF'
// besinci/cozumleyiciler/sozlesme-varsayilani.mjs — besinci cozumleyici: sozlesmede yazili varsayilan bolge
const SOZLESME = { "M-118": "orta", "M-204": "yakin" };

export const sozlesmeVarsayilani = { ad: "sozlesme-varsayilani", coz: (oku) => {
  const b = SOZLESME[oku("adres").musteri];
  return b === undefined ? null : { guven: 0.4, yaz: { bolge: b } };
} };
EOF
python3 - <<'EOF'
from pathlib import Path

GECMIS = 'import { gecmisGonderi } from "./cozumleyiciler/gecmis-gonderi.mjs";'
YENI = 'import { sozlesmeVarsayilani } from "./cozumleyiciler/sozlesme-varsayilani.mjs";'
CIFT = [(GECMIS, f"{GECMIS}\n{YENI}"), ("gecmisGonderi];", "gecmisGonderi, sozlesmeVarsayilani];")]
for ad in ["kur.mjs", "dogrudan.mjs"]:
    yol = Path("besinci") / ad
    metin = yol.read_text().replace("// kara-tahta/", "// besinci/")
    for eski, yeni in CIFT:
        metin = metin.replace(eski, yeni)
    yol.write_text(metin)
EOF
diff -rq kara-tahta besinci
node -e 'Promise.all([import("./besinci/kur.mjs"), import("./besinci/dogrudan.mjs")])
  .then(([k, d]) => console.log(`besinci agac: kur listesi = ${k.COZUMLEYICILER.length}, dogrudan sira = ${d.SIRA.length}`))'
Only in besinci/cozumleyiciler: sozlesme-varsayilani.mjs
Files kara-tahta/dogrudan.mjs and besinci/dogrudan.mjs differ
Files kara-tahta/kur.mjs and besinci/kur.mjs differ
besinci agac: kur listesi = 5, dogrudan sira = 5

Bir dosya eklendi ve iki dosya düzenlendi: bileşim kökü ile doğrudan çağrı düzeni. Tahta, denetim döngüsü, ücret dönüşümü ve dört çözümleyici hiç değişmedi. Dosya sayısı iki düzende aynı, cinsi değil — biri yalnız bir liste, öteki çağrı sırası ile seçim kuralı.

Ödünleşimin Sayıları

Kazanç iki kalite niteliğinde ölçüldü: değiştirilebilirlikte dokuz dosyanın yedisi başka birim adı taşımıyor ve beşinci çözümleyici tahtayı, denetim döngüsünü ve dört çözümleyiciyi hiç düzenletmedi; bütünlükte 24 sıralamanın hepsinde katkı 4.

Bedel üç kalemdedir. Fazladan iş: 3 tur, 5 yoklama ve 7 okumaya karşılık doğrudan çağrının 1 turu, 4 yoklaması ve 5 okuması. Belirlenimcilik: yazma kuralı yanlış seçildiğinde aynı adres üç ayrı ücret üretti. Görünürlük: sonucu hangi çözümleyicinin belirlediği yalnız tahtanın kaydından okunur.

Özet

  • Kara tahta düzeni üç parçadan oluşur: kısmi sonuçların yazıldığı ortak alan, birbirinin adını bilmeyen çözümleyiciler ve ne yaptıklarını bilmeyen bir denetim döngüsü; yazan taraf kimin okuyacağını bilmediği için bu, gözlemci kalıbındaki bildirimden farklıdır.
  • Dokuz dosyanın yedisinde başka birim adı 0; adları bilen iki dosya bileşim kökü ile doğrudan çağrı düzeni ve ikisi de 4 ad taşıyor.
  • Aynı sonuç için kara tahta 3 tur, 5 yoklama ve 7 okuma harcadı, doğrudan çağrı 1 tur, 4 yoklama ve 5 okuma; adres anahtarını 4 birim okudu ve 1 tur boşa döndü.
  • İlk yazan kazanır kuralıyla 24 sıralama 3 ayrı bölge ve 3 ayrı tutar (8.500, 11.450, 15.300) üretti; en güvenli kazanır kuralıyla hepsi tek bölge ve tek tutar verdi.
  • Katkı kara tahtada 24 sıralamanın hepsinde 4, doğrudan çağrıda 12 sıralamada 3; beşinci çözümleyici 1 dosya ekletti ve 2 dosya düzenletti.

Sonraki Adım

Bu konu birimlerin nasıl düzenlendiğini ve bağımlılık okunun hangi yöne baktığını ele aldı: katmanlı sıralar, içe bakan halkalar, dağıtılabilir bileşenler, takılabilir eklentiler ve birbirini hiç tanımayan çözümleyiciler. Kara tahta düzeninde birimler ortak bir bilgi alanına yazıyor, oradan okuyor ve buluşmayı alanın kendisi sağlıyordu. Sıradaki konu düzeni değil konuşma biçimini sorar: iki birim arasındaki bir etkileşimde kim başlatır, kim bekler ve karşı taraf hakkında bilinmesi zorunlu olan ne kadardı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