Değişkenlik, bir bileşik türün alt tiplemesinin, parçalarının alt tiplemesiyle nasıl ilişkili olduğunu açıklar — yani Container<Sub> ne zaman Container<Super> atanabilir?
Dog öğesinin Animal öğesinin bir alt türü olduğunu varsayalım.
Değişkenlik, bir bileşik türün alt tiplemesinin, parçalarının alt tiplemesiyle nasıl ilişkili olduğunu açıklar — yani Container<Sub> ne zaman Container<Super> atanabilir?
Dog öğesinin Animal öğesinin bir alt türü olduğunu varsayalım.
let dogs: Dog[] = [];
let animals: Animal[] = dogs; // ✅ Dog[] is assignable to Animal[]
Dönüş türleri ve diziler kovaryanttır: Dog ⊆ Animal ise, Dog[] ⊆ Animal[]. Dog döndüren bir işlev, Animal döndüren birin beklendiği yerde kullanılabilir.
type Handler<T> = (arg: T) => void;
let animalHandler: Handler<Animal> = (a) => {};
let dogHandler: Handler<Dog> = animalHandler; // ✅ (with strictFunctionTypes)
// a handler that accepts ANY Animal can safely handle a Dog
İşlev parametreleri kontravaryant: Handler<Animal> Handler<Dog> atanabilir, bu eleman ilişkisinin tersidir. Bu sağlamdır — tüm hayvanları işleyen bir şey kesinlikle köpekleri işler.
// Method parameters in TS are bivariant by default (a known unsound convenience)
interface Comparer<T> { compare(a: T): void; }
TypeScript, tek başına işlev türlerini strictFunctionTypes altında yalnızca kontravaryant olarak kontrol eder; metot parametreleri ergonomi için kasıtlı olarak bivaryant olup, bu teknik olarak sağlam değildir.
Değişkenlik, belirli atamaların neden izin verildiğini veya reddedildiğini açıklar — neden Dog[] Animal[] düşer, ancak (d: Dog) => void geri araması her zaman (a: Animal) => void yerine geçemez.
Bunu anlamak, genel API ler tasarlamanıza (ör. salt okunur vs yazma konumları) ve işlevleri ve genel türleri içeren kafa karıştırıcı "atanamazlık" hatalarını çözmenize yardımcı olur.