İçeriğe geç
academia.sh

Ders 18 / 23

Özyinelemeli Tipler

Kendine başvuran tip tanımları, derin dönüşümler, tip düzeyinde özyineleme ile anahtar yolu üretimi ve derleyicinin açılım derinliği sınırı.

İçindekiler

Ölçüm kayıtları her zaman düz bir yapı taşımaz: bir kaydın altında alt kayıtlar, bir yapılandırma nesnesinin altında iç içe bölümler bulunur. Böyle yapıların tipi, kendisine başvurmak zorundadır.

Tip Takma Adları dersinde bu olanağın sınırı belirlenmişti: nesne alanı içindeki başvuru geçerli, doğrudan başvuru geçersizdi. Bu ders, o olanağın ne kadar ileri götürülebileceğini — derin dönüşümler ve tip düzeyinde özyineleme — ve derleyicinin nerede durduğunu ele alır.

Kendine Başvuran Veri Tipleri

Yapısı önceden bilinmeyen, iç içe geçebilen veri için tipik örnek JSON’un kendisidir:

type JsonDegeri =
  | string
  | number
  | boolean
  | null
  | JsonDegeri[]
  | { [anahtar: string]: JsonDegeri };

function derinlik(deger: JsonDegeri): number {
  if (Array.isArray(deger)) {
    return 1 + Math.max(0, ...deger.map(derinlik));
  }
  if (typeof deger === "object" && deger !== null) {
    return 1 + Math.max(0, ...Object.values(deger).map(derinlik));
  }
  return 0;
}

const kayit: JsonDegeri = {
  kimlik: "s-01",
  deger: 21.4,
  etiketler: ["kazan", "vardiya-3"],
  konum: { bina: "A", kat: 2 },
};

console.log(derinlik(kayit));
console.log(derinlik(21.4));

Çıktı:

2
0

Tip, altı üyeli bir birleşimdir ve son iki üyesi kendisine başvurur. Başvurular birer alan tipi içinde olduğu için derleyici onları yalnızca gerektiğinde çözer; sonsuz açılım oluşmaz.

Tipin bir güvencesi de vardır. Dosyanın sonuna, bir boş satırdan sonra const gecersiz: JsonDegeri = { zaman: new Date() }; satırı eklenirse:

s1.ts(29,7): error TS2322: Type '{ zaman: Date; }' is not assignable to type 'JsonDegeri'.
  Types of property 'zaman' are incompatible.
    Type 'Date' is not assignable to type 'JsonDegeri | undefined'.
      Type 'Date' is not assignable to type '{ [anahtar: string]: JsonDegeri; }'.
        Index signature for type 'string' is missing in type 'Date'.

Bu, dizileştirilebilir veriyi dizileştirilemeyenden ayıran bir sınamadır. JSON.stringify çağrısı bir tarih nesnesini sessizce dizgiye çevirir ve ayrıştırma sonrası tip değişir; JsonDegeri tipi bu sessiz dönüşümü derleme anına taşır.

derinlik fonksiyonu da özyinelemelidir ve tipin yapısını izler: Programlama Temelleri kursundaki özyineleme dersinde kurulan taban durumu ile indirgeme adımı, burada tipin üyelerine karşılık gelir.

Derin Dönüşümler

Yardımcı tipler dersindeki Readonly yalnızca birinci düzeye uygulanır:

interface Yapilandirma {
  ad: string;
  esikler: { alt: number; ust: number };
}

const sig: Readonly<Yapilandirma> = {
  ad: "kazan-2",
  esikler: { alt: -40, ust: 85 },
};

sig.esikler.ust = 90;
console.log(sig.esikler.ust);

Bu dosya tip denetiminden geçer ve 90 yazar. sig.esikler alanı salt okunurdur, ancak işaret ettiği nesnenin alanları değildir.

Özyinelemeli bir eşlenmiş tip bu boşluğu kapatır:

type DerinSaltOkunur<T> = {
  readonly [A in keyof T]: T[A] extends object ? DerinSaltOkunur<T[A]> : T[A];
};

interface Yapilandirma {
  ad: string;
  esikler: { alt: number; ust: number };
  kanallar: { sensor: string; birim: string }[];
}

const yapilandirma: DerinSaltOkunur<Yapilandirma> = {
  ad: "kazan-2",
  esikler: { alt: -40, ust: 85 },
  kanallar: [{ sensor: "sicaklik", birim: "C" }],
};

console.log(yapilandirma.esikler.ust, yapilandirma.kanallar[0].sensor);

Çıktı 85 sicakliktır. Dosyanın sonuna, bir boş satırdan sonra yapilandirma.esikler.ust = 90; satırı eklenirse:

s3.ts(19,22): error TS2540: Cannot assign to 'ust' because it is a read-only property.

Eşleme her alanda T[A] extends object koşulunu sınar; nesne olan alanlarda kendini yeniden uygular, ilkel alanlarda durur. Diziler de nesne olduğu için kanallar alanı readonly bir diziye dönüşür ve elemanlarının alanları da salt okunur olur.

Önceki derslerdeki uyarı yine geçerlidir: bu güvence derleme anına aittir. Yapılandırma nesnesi çalışma zamanında değiştirilebilir; derinlemesine dondurmak ayrı bir işlemdir.

Tip Düzeyinde Özyineleme

Özyineleme yalnızca veri biçimi tanımlamakla sınırlı değildir; tip düzeyinde hesap yapmak için de kullanılır. İç içe bir nesnenin tüm anahtar yollarını üreten tip, bunun tipik örneğidir:

type Yol<T> = T extends object
  ? {
      [A in keyof T & string]: T[A] extends object ? A | `${A}.${Yol<T[A]>}` : A;
    }[keyof T & string]
  : never;

interface Yapilandirma {
  ad: string;
  esikler: { alt: number; ust: number };
  ag: { adres: string; kimlik: { kullanici: string } };
}

const gecerli: Yol<Yapilandirma>[] = [
  "ad",
  "esikler",
  "esikler.alt",
  "ag.kimlik.kullanici",
];
console.log(gecerli.length);

Çıktı 4tür. Dosyanın sonuna, bir boş satırdan sonra const yanlisYol: Yol<Yapilandirma> = "esikler.orta"; satırı eklenirse:

s6.ts(21,7): error TS2322: Type '"esikler.orta"' is not assignable to type '"ad" | "esikler" | "ag" | "esikler.alt" | "esikler.ust" | "ag.adres" | "ag.kimlik" | "ag.kimlik.kullanici"'.

Tanı, hesabın tam sonucunu yazıyor: sekiz geçerli yol. Yapı üç dersin bileşimidir. Eşlenmiş tip her alan için bir değer üretir; [keyof T & string] indeksli erişimi bu değerlerin birleşimini alır; şablon dizgi tipi alt yolları öne eklenmiş ad ile birleştirir; koşullu tip nesne olmayan alanlarda özyinelemeyi durdurur.

Bu tipin kullanım yeri, yapılandırma anahtarını dizgi olarak alan bir arayüzdür. Yol dizgisi artık serbest bir dizgi değil, kapalı bir kümedir; yanlış yazılmış bir anahtar derleme anında yakalanır.

Derinlik Sınırı

Derleyici sonsuz açılımı önlemek için bir üst sınır uygular. Sınır, uzunluğu sayan bir demet kurarak gözlenebilir:

type Yinele<N extends number, B extends unknown[] = []> = B["length"] extends N
  ? B
  : Yinele<N, [...B, unknown]>;

type Sonuc = Yinele<1001>;
declare const s: Sonuc;
console.log(s.length);
s10.ts(5,14): error TS2589: Type instantiation is excessively deep and possibly infinite.

Aynı dosyada Yinele<999> yazıldığında tanı gelmez. Sınırın tam değeri derleyici sürümüne bağlıdır ve buna dayanan kod kırılgandır; önemli olan bir sınırın bulunmasıdır.

Sınırın altında kalan özyinelemelerin de bir bedeli vardır: her açılım derleyicinin çalışma süresini artırır. Yol üretimi gibi tipler, geniş yapılandırma nesnelerinde tip denetimini gözle görülür biçimde yavaşlatabilir. Algoritmalar kursundaki zaman ve alan ödünleşimi burada derleme süresi ile tip kesinliği arasında kurulur.

Sınırsız derinlik gerektiren durumlar için tip düzeyi çözüm uygun değildir; doğru araç, çalışma zamanı doğrulaması ve markalı tiptir.

Özyineleme Yazarken

Üç kural, özyinelemeli tiplerin sağlıklı kalmasını sağlar.

Taban durumu açık yazılır. Yukarıdaki her örnekte özyinelemenin durduğu bir koşul vardır: JsonDegeri için ilkel üyeler, DerinSaltOkunur için extends object sınaması, Yol için aynı sınama. Taban durumu olmayan bir tip, derleyicinin sınırına kadar açılır ve TS2589 verir.

Derinlik veriden gelmemelidir. Açılım derinliği tipin yapısına bağlı olmalı, çağıranın verdiği bir sayıya değil. Yinele<N> örneği bilinçli olarak bu kuralı çiğneyerek sınırı gösterir.

Sonuç sınanır. Tip düzeyinde hesaplanan bir tipin doğruluğu, beklenen değerleri içeren bir dizi bildirerek sınanabilir — yukarıdaki gecerli bildirimi bunu yapar. Yanlış hesaplanan bir tip, kullanıldığı her yerde şaşırtıcı tanılar üretir.

Özet

  • Nesne alanı içindeki kendine başvuru geçerlidir; derleyici bu başvuruları yalnızca gerektiğinde çözer.
  • Readonly gibi yardımcı tipler yalnızca birinci düzeye uygulanır; derin dönüşüm için özyinelemeli eşlenmiş tip yazılır.
  • Eşlenmiş tip, indeksli erişim, şablon dizgi tipi ve koşullu tip birlikte, iç içe anahtar yollarını kapalı bir birleşim olarak üretebilir.
  • Derleyici belirli bir açılım derinliğinden sonra TS2589 tanısı verir; sınırın değeri sürüme bağlıdır ve ona dayanan kod kırılgandır.
  • Özyinelemeli tipte taban durumu açıkça yazılır, derinlik veriden değil yapıdan gelir ve sonuç ayrı bir bildirimle sınanır.

Sonraki Adım

Buraya kadar tüm örnekler tek dosyalık ve elle verilmiş derleyici seçenekleriyle çalıştı. Gerçek bir projede seçenekler bir yapılandırma dosyasında toplanır ve katılık düzeyi bilinçli seçilir. Sonraki konu proje yapılandırmasını ele alacak: hangi bayrağın hangi hata sınıfını yakaladığı, bildirim dosyalarının rolü, JavaScript ile birlikte çalışma ve tip denetimi ile yayın çıktısının ayrılması.

İ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