İçeriğe geç
academia.sh

Ders 12 / 23

Genellikler

Tip parametreleriyle biçimden bağımsız yapılar yazma, tip argümanı çıkarımı, kısıtlar, keyof kısıtı, varsayılan tip parametreleri ve genelliğin kötüye kullanımı.

İçindekiler

Önceki konudaki tiplerin tümü belirliydi: Olcum kayıtları tutan bir defter, Olcum alan bir süzgeç. Oysa defterin yapısı kayıt tipinden bağımsızdır — aynı kod ölçümleri, uyarıları veya olayları toplamalıdır.

Bunu sağlamanın iki kötü yolu vardır. Her kayıt tipi için ayrı bir defter yazmak kodu çoğaltır; deftere any[] kullandırmak ise tip güvencesini kaldırır. Genellik (generic), üçüncü yolu verir: yapıyı bir tip parametresiyle yazmak ve tipi kullanım yerinde vermek.

Tip Parametreli Yapılar

Tip parametresi, bildirim adından sonra açılı ayraç içinde yazılır ve gövde boyunca bir tip gibi kullanılır:

interface Olcum {
  kimlik: string;
  deger: number;
}

class Defter<T> {
  private kayitlar: T[] = [];

  ekle(kayit: T): void {
    this.kayitlar.push(kayit);
  }

  sonuncu(): T | undefined {
    return this.kayitlar.at(-1);
  }

  get sayi(): number {
    return this.kayitlar.length;
  }
}

const olcumler = new Defter<Olcum>();
olcumler.ekle({ kimlik: "s-01", deger: 21.4 });
console.log(olcumler.sonuncu()?.deger, olcumler.sayi);

const uyarilar = new Defter<string>();
uyarilar.ekle("sensor yanit vermiyor");
console.log(uyarilar.sonuncu()?.toUpperCase());

Çıktı:

21.4 1
SENSOR YANIT VERMIYOR

Tek bir sınıf iki farklı kayıt tipiyle kullanıldı ve her kullanımda tip bilgisi korundu: olcumler.sonuncu() çağrısı Olcum | undefined, uyarilar.sonuncu() çağrısı string | undefined verir. Dosyanın sonuna, bir boş satırdan sonra olcumler.ekle("s-02"); satırı eklenirse:

m1.ts(30,15): error TS2345: Argument of type 'string' is not assignable to parameter of type 'Olcum'.

any[] çözümü bu tanıyı üretmezdi. Genellik, ortak yapıyı paylaşırken tip güvencesini kaybetmemenin yoludur.

Dönüş tipinin T | undefined olması da tasarımın parçası: at(-1) çağrısı boş bir dizide undefined verir ve tip bunu kaydeder. Çağıran, ?. yazımıyla bu olasılığı ele almak zorundadır.

Tip Argümanı Çıkarımı

Tip argümanı çoğu zaman yazılmaz; derleyici argümanlardan çıkarır:

function ilk<T>(kayitlar: readonly T[]): T | undefined {
  return kayitlar[0];
}

const sayi = ilk([21.4, 22.1]);
const metin = ilk(["s-01", "s-02"]);
console.log(sayi?.toFixed(1), metin?.toUpperCase());

Çıktı 21.4 S-01dir.

ilk<number> ve ilk<string> yazımlarına gerek kalmamıştır. sayi bildiriminin tipi number | undefined, metin bildiriminin tipi string | undefined olarak çıkarılır ve her birinde o tipin yöntemleri kullanılabilir.

Çıkarımın çalışması için tip parametresinin argümanlarda geçmesi gerekir. Yalnızca dönüş tipinde geçen bir tip parametresi çıkarılamaz; bu durumun tehlikesi dersin sonunda ele alınacaktır.

Kısıtlar

Tip parametresi hiçbir şey bilinmeyen bir tiptir; üzerinde işlem yapılamaz. Belirli üyelere erişmek için parametreye kısıt (constraint) yazılır:

interface Kimlikli {
  kimlik: string;
}

function kimlikleriTopla<T extends Kimlikli>(kayitlar: readonly T[]): string[] {
  return kayitlar.map((k) => k.kimlik);
}

console.log(kimlikleriTopla([{ kimlik: "s-01", deger: 21.4 }]));
console.log(kimlikleriTopla([{ ad: "kazan-2" }]));
m4.ts(10,32): error TS2353: Object literal may only specify known properties, and 'ad' does not exist in type 'Kimlikli'.

T extends Kimlikli bildirimi iki iş yapar: gövdede k.kimlik erişimini olanaklı kılar ve uymayan argümanları çağrı yerinde reddeder. Dokuzuncu satırın kabul edilmesi de önemli — deger alanı fazlalıktır, kısıt yalnızca en az gerekliliği belirtir.

Kısıt yazmadan k.kimlik yazılsaydı derleyici erişimi reddederdi: T herhangi bir tip olabilir, dizgi de olabilir, sayı da.

keyof ile Kısıt

Kısıtlar başka tip parametrelerine de başvurabilir. En kullanışlı biçimi, bir tipin alan adlarını veren keyof işleciyle kurulur:

interface Olcum {
  kimlik: string;
  deger: number;
  birim: "C" | "Pa" | "%";
}

function alan<T, A extends keyof T>(kayit: T, ad: A): T[A] {
  return kayit[ad];
}

const kayit: Olcum = { kimlik: "s-01", deger: 21.4, birim: "C" };
const d: number = alan(kayit, "deger");
const b: "C" | "Pa" | "%" = alan(kayit, "birim");
console.log(d, b);

alan(kayit, "konum");
m5.ts(16,13): error TS2345: Argument of type '"konum"' is not assignable to parameter of type 'keyof Olcum'.

Üç yeni yazım var. keyof T, T tipinin alan adlarının sabit tiplerinden oluşan bir birleşimdir — burada "kimlik" | "deger" | "birim". A extends keyof T kısıtı, ikinci argümanı bu adlarla sınırlar. T[A] ise indeksli erişim tipidir (indexed access type): A adlı alanın tipini verir.

Sonuç, alan adına göre değişen bir dönüş tipidir. alan(kayit, "deger") çağrısının tipi number, alan(kayit, "birim") çağrısının tipi "C" | "Pa" | "%"tir. Aşırı yükleme yazmadan, alan sayısı kadar farklı imza elde edilmiştir.

Varsayılan Tip Parametresi

Tip parametrelerine varsayılan değer verilebilir. Sınırda doğrulamanın sonucunu taşıyan tip bunun tipik kullanımıdır:

type Sonuc<D, H = string> =
  | { durum: "basarili"; deger: D }
  | { durum: "hatali"; hata: H };

interface Olcum {
  kimlik: string;
  deger: number;
}

function oku(ham: string): Sonuc<Olcum> {
  const parcalar = ham.split("=");
  if (parcalar.length !== 2) {
    return { durum: "hatali", hata: `bicim gecersiz: ${ham}` };
  }
  const deger = Number(parcalar[1]);
  if (!Number.isFinite(deger)) {
    return { durum: "hatali", hata: `sayi degil: ${parcalar[1]}` };
  }
  return { durum: "basarili", deger: { kimlik: parcalar[0], deger } };
}

function yaz(sonuc: Sonuc<Olcum>): string {
  return sonuc.durum === "basarili"
    ? `${sonuc.deger.kimlik}=${sonuc.deger.deger}`
    : `hata: ${sonuc.hata}`;
}

console.log(yaz(oku("s-01=21.4")));
console.log(yaz(oku("bozuk")));
console.log(yaz(oku("s-02=abc")));

Çıktı:

s-01=21.4
hata: bicim gecersiz: bozuk
hata: sayi degil: abc

Sonuc<Olcum> yazımında ikinci tip argümanı verilmemiş, varsayılan string kullanılmıştır. Hata tipinin ayrıntılanması gerektiğinde Sonuc<Olcum, HataKodu> yazılır ve yaz fonksiyonu dışındaki kod değişmez.

Bu tip, ilk derste konan sınır kuralının olgunlaşmış hâlidir. oku fonksiyonu ne bir istisna atar ne de null döndürür; başarısızlığı tipin bir üyesi olarak taşır ve çağıranın onu ele almasını derleyici zorunlu kılar. Ayırt edici birleşim burada bir hata yönetimi aracına dönüşmüştür.

Genelliğin Kötüye Kullanımı

Genellik her yerde iyileştirme sağlamaz. İki karşı örnek vardır.

Tek kez geçen tip parametresi. function yazdir<T>(deger: T): void imzasında T yalnızca bir parametrede geçer ve hiçbir bağıntı kurmaz; unknown yazmak aynı işi daha açık yapar. Genelliğin değeri, iki nokta arasında bir bağ kurmasındadır: girdi tipi ile çıktı tipi, ya da iki parametre.

Yalnızca dönüş tipinde geçen tip parametresi. Bu, tip iddiasının gizlenmiş hâlidir:

function tehlikeli<T>(ham: unknown): T {
  return ham as T;
}

const kimlik = tehlikeli<string>(42);
console.log(kimlik.toUpperCase());

Dosya tip denetiminden geçer, çalıştırıldığında ise:

TypeError: kimlik.toUpperCase is not a function

Çağıran T olarak ne yazarsa yazsın derleyici kabul eder, çünkü gövdedeki as T yazımı denetimi susturur. Bu imza, bir doğrulama vaat ediyor gibi görünüp hiçbir şey doğrulamaz — any döndürmekten daha tehlikelidir, çünkü tip güvenli görünür.

Kural: tip parametresi en az iki yerde geçmelidir. Yalnızca dönüş tipinde geçiyorsa, imza unknown döndürmeli ve çağıran daraltmayı kendisi yapmalıdır.

Özet

  • Tip parametresi, bir yapının biçimini kayıt tipinden bağımsız yazmayı sağlar ve any çözümünün aksine tip güvencesini korur.
  • Tip argümanları çoğu zaman çağrı argümanlarından çıkarılır; çıkarım için parametrenin argüman listesinde geçmesi gerekir.
  • Kısıt (T extends K), tip parametresi üzerinde işlem yapılabilmesini sağlar ve uymayan argümanları çağrı yerinde reddeder.
  • keyof T alan adlarının birleşimini, T[A] indeksli erişim tipini verir; ikisi birlikte alan adına göre değişen dönüş tipleri kurar.
  • Varsayılan tip parametresi yaygın durumu kısaltır; ayırt edici birleşimle kurulan sonuç tipi, başarısızlığı tipin bir üyesi olarak taşır.
  • Tip parametresi en az iki yerde geçmelidir; yalnızca dönüş tipinde geçen bir parametre, gizlenmiş bir tip iddiasıdır.

Sonraki Adım

Bu derste birkaç kez bir birleşim tipinin üzerinde sınama yapıldı ve derleyici tipi tek bir üyeye indirdi. Bu indirgemenin kuralları henüz sıralanmadı: hangi sınamalar kanıt sayılır, kanıt ne kadar uzağa taşınır, kendi yazdığınız doğrulama fonksiyonunun sonucu derleyiciye nasıl bildirilir? Sonraki ders daraltmayı ayrıntılı ele alacak.

İ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