İçeriğe geç
academia.sh

Ders 04 / 23

Tip Çıkarımı

Derleyicinin ilk değerden, bağlamdan ve dönüş ifadelerinden tip çıkarma kuralları; genişletme davranışı ve tip açıklamasının hangi noktalarda gerekli olduğu.

İçindekiler

Önceki derslerin örneklerinde derleyici, yazılmamış birçok tipi biliyordu: daraltma sonrası alanların tipleri, dizi elemanlarının tipi, reduce geri çağrısının parametreleri. Bu bilgiyi nereden aldı?

Yanıt tip çıkarımıdır (type inference): derleyici, bir ifadenin tipini çevresindeki bilgiden hesaplar. Çıkarım TypeScript’i kullanılabilir kılan şeydir — her değişkene tip yazmak gerekseydi tip katmanının maliyeti yararını aşardı. Bu ders çıkarımın kurallarını, sınırlarını ve tip açıklamasının hâlâ gerektiği noktaları ele alır.

Çıkarılan Tipi Görmek

Çıkarımı incelemenin en doğrudan yolu, derleyiciden bildirim dosyası üretmesini istemektir. Bildirim dosyası, bir modülün dışa aktardığı adların tiplerini yazar; çıkarılan tipler orada açıkça görünür.

export const birim = "C";
export let degisken = "C";
export const deger = 21.4;
export const olcum = { kimlik: "s-01", deger: 21.4, birim: "C" };
export const degerler = [21.4, 22.1];
export const karisik = [21.4, "yok"];

export function ortalama(olcumler: number[]) {
  return olcumler.reduce((t, d) => t + d, 0) / olcumler.length;
}

Dosya cikarim.ts adıyla kaydedilip tsc --strict --target es2022 --declaration --emitDeclarationOnly cikarim.ts komutuyla derlendiğinde yanına cikarim.d.ts yazılır:

export declare const birim = "C";
export declare let degisken: string;
export declare const deger = 21.4;
export declare const olcum: {
    kimlik: string;
    deger: number;
    birim: string;
};
export declare const degerler: number[];
export declare const karisik: (string | number)[];
export declare function ortalama(olcumler: number[]): number;

Bu dosya, çıkarımın altı kuralını tek bakışta gösterir. Sırayla ele alalım.

İlk Değerden Çıkarım ve Genişletme

birim sabitinin tipi string değil "C"dir — yalnızca tek bir değer içeren küme. deger sabitinin tipi de number değil 21.4tür. Buna karşılık degisken bildiriminin tipi stringe genişletilmiştir (widening).

Kural şudur: değeri değişebilen bir bildirimde sabit tip tutulamaz, çünkü bildirim daha sonra aynı tipte başka bir değer alabilir. let degisken = "C" bildiriminden sonra degisken = "Pa" yazılabilir; tip "C" kalsaydı bu atama reddedilirdi. Değeri değişemeyen bir bildirimde ise genişletmeye gerek yoktur.

Aynı gerekçe nesne alanları için de geçerlidir. olcum sabiti const ile bildirilmiş olmasına rağmen birim alanının tipi "C" değil string çıkarılmıştır. Nedeni, JavaScript Temelleri kursunda kurulan ayrımdır: const bildirimi bağlamayı sabitler, nesnenin içeriğini değil. olcum.birim = "Pa" yazımı geçerli bir JavaScript ifadesidir, dolayısıyla alanın tipi genişletilir.

Bu davranışın doğrudan bir sonucu vardır:

type Birim = "C" | "Pa" | "%";
type Olcum = { kimlik: string; deger: number; birim: Birim };

const ham = { kimlik: "s-01", deger: 21.4, birim: "C" };
const kayit: Olcum = ham;
console.log(kayit);
d4.ts(5,7): error TS2322: Type '{ kimlik: string; deger: number; birim: string; }' is not assignable to type 'Olcum'.
  Types of property 'birim' are incompatible.
    Type 'string' is not assignable to type 'Birim'.

Atama başarısız olur, çünkü ham.birim alanının tipi stringe genişlemiştir ve string, üç değerli Birim kümesine sığmaz. Genişletmeyi engellemenin yolu Sabit Tipler ve as const dersinin konusudur.

Dizilerde çıkarım, elemanların tiplerinin birleşimini alır: degerler için number[], karışık elemanlı karisik için (string | number)[]. Dizi de değiştirilebilir olduğu için eleman tipleri genişletilir.

Bağlamsal Tipleme

Çıkarım tek yönlü değildir. Bir ifadenin tipi, o ifadenin beklendiği yerden de gelebilir. Buna bağlamsal tipleme (contextual typing) denir:

const degerler: number[] = [21.4, 22.1, 23.0];

degerler.forEach((d) => console.log(d.toFixed(1)));
degerler.forEach((d) => console.log(d.toUpperCase()));
d1.ts(4,39): error TS2339: Property 'toUpperCase' does not exist on type 'number'.

d parametresine tip yazılmamıştır. Derleyici tipi, forEach yönteminin beklediği geri çağrı tipinden alır: degerler bir number[] olduğuna göre geri çağrının ilk parametresi numberdır. Üçüncü satır geçerlidir, dördüncü satır dizgi yöntemi çağırdığı için reddedilir.

Bağlamsal tipleme, örtük any denetiminin neden geri çağrılarda tetiklenmediğini de açıklar: parametre tipsiz görünse de bağlamdan gelen bir tipi vardır.

Dönüş Tipi Çıkarımı ve Hatanın Yeri

Fonksiyon dönüş tipleri de çıkarılır. Yukarıdaki bildirim dosyasında ortalama fonksiyonunun dönüş tipi yazılmadığı hâlde number olarak görünüyor.

Çıkarımın rahatlığının bir bedeli vardır ve bu bedel, hatanın nerede bildirildiğinde ortaya çıkar. Önce dönüş tipi yazılmamış hâli:

function olcumOlustur(kimlik: string, ham: string) {
  return { kimlik, deger: ham };
}

const kayit = olcumOlustur("s-01", "21.4");
const yuvarlanmis: string = kayit.deger.toFixed(1);
console.log(yuvarlanmis);
d2.ts(6,41): error TS2551: Property 'toFixed' does not exist on type 'string'. Did you mean 'fixed'?

Hata altıncı satırda, yani fonksiyonun kullanıldığı yerde bildirilir. Oysa yanlış olan şey ikinci satırdadır: deger alanına dizgi konmuştur. Aynı fonksiyon, modelin tipiyle birlikte yazıldığında:

type Olcum = { kimlik: string; deger: number };

function olcumOlustur(kimlik: string, ham: string): Olcum {
  return { kimlik, deger: ham };
}

console.log(olcumOlustur("s-01", "21.4"));
d3.ts(4,20): error TS2322: Type 'string' is not assignable to type 'number'.

Tanı artık dördüncü satırda — hatanın kaynağında. Fark, tek bir fonksiyonda önemsiz görünür; onlarca modülden geçen bir çağrı zincirinde ise hatanın kaynağını bulma süresini belirler.

Buradan pratik bir kural çıkar: dışa açılan sınırlarda tip yazılır, iç ayrıntılarda çıkarıma bırakılır. Bir modülün dışa aktardığı fonksiyonların parametre ve dönüş tipleri, bir sınıfın genel yöntemleri ve modülün veri tipleri elle yazılır. Yerel değişkenler, geri çağrı parametreleri ve özel yardımcı fonksiyonlar çıkarıma bırakılır.

Bu kuralın ikinci gerekçesi, tip açıklamasının bir sözleşme olmasıdır. Yazılmış bir dönüş tipi, gövde değiştiğinde sözleşmenin bozulup bozulmadığını sınar. Çıkarılan dönüş tipi ise gövdeyle birlikte sessizce değişir — ve sözleşmenin değiştiği, ancak kullanan kodda bir hata belirdiğinde fark edilir.

Çıkarımın Sınırları

Çıkarım her boşluğu dolduramaz. Üç tipik durum vardır.

Boş kapla başlayan birikeç. Yerel bir const gecerliler = [] bildiriminde derleyici eleman tipini sonraki eklemelerden toparlar. Bu, hatanın yine uzağa taşınması demektir:

type Olcum = { kimlik: string; deger: number };

function suz(kayitlar: Olcum[]): Olcum[] {
  const gecerliler = [];
  for (const k of kayitlar) {
    if (k.deger > 0) {
      gecerliler.push(k.kimlik);
    }
  }
  return gecerliler;
}

console.log(suz([{ kimlik: "s-01", deger: 21.4 }]));
bos2.ts(10,3): error TS2322: Type 'string[]' is not assignable to type 'Olcum[]'.
  Type 'string' is not assignable to type 'Olcum'.

Yanlış olan yedinci satırdır — kaydın kendisi yerine kimliği eklenmiştir — ama tanı onuncu satırda çıkar. const gecerliler: Olcum[] = [] yazılsaydı tanı eklemenin yapıldığı satırda görünürdü.

Dışarıdan gelen veri. JSON.parse çağrısının dönüş tipi anydir; çıkarımın kullanacağı bir bilgi yoktur. Önceki derste kurulan kural burada geçerlidir — dönüş unknown olarak alınır ve sınanır.

Aşırı geniş çıkarım. Bir nesne sabitinden çıkarılan tip genellikle amaçlanandan gevşektir; yukarıdaki birim: string örneği bunun tipik hâlidir. Amaçlanan tip yazıldığında derleyici hem daraltır hem de sabitin o tipe uyduğunu sınar.

Özet

  • Tip çıkarımı, bir ifadenin tipini ilk değerinden, bağlamından ve dönüş ifadelerinden hesaplar; çıkarılan tipler bildirim dosyası üretilerek görüntülenebilir.
  • Değeri değişebilen bildirimlerde sabit tipler genişletilir; const bildirimi bağlamayı sabitlediği için sabit tip korunur, ancak nesne alanları yine genişletilir.
  • Bağlamsal tipleme, bir ifadenin tipini beklendiği yerden alır; geri çağrı parametreleri bu yolla tiplenir.
  • Dönüş tipi yazılmadığında hata, fonksiyonun kullanıldığı yerde bildirilir; yazıldığında kaynağında bildirilir.
  • Dışa açılan sınırlarda tip yazılır, iç ayrıntılar çıkarıma bırakılır; tip açıklaması aynı zamanda gövde değiştiğinde sınanan bir sözleşmedir.

Sonraki Adım

Ölçüm kaydının birim alanı üç değerden birini alabiliyordu ve bu, birbirinin alternatifi olan tiplerin birleştirilmesiyle yazılmıştı. Aynı işlemin bir de kesişim yönü vardır: iki tipin gerekliliklerini aynı anda taşıyan değerler. Sonraki ders, birleşim ve kesişim tiplerinin kurallarını, birleşim üzerinde hangi işlemlerin yapılabildiğini ve kesişimin ne zaman boşa düştüğünü 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