TypeScript'te generik argümanlarının yerleştirilmesi arasındaki fark


Genel fonksiyon sözdizimi arasındaki fark nedir:

type Identity<T> = (t: T) => T

ve

type Identity = <T>(t: T) => T

?

Cevap #1

TypeScript'te iki farklı türde generic vardır: generic fonksiyonlar ve generic tipler.


Genel bir fonksiyon, genel tür parametresini (veya parametrelerini) fonksiyonun çağrı imzasında bildirir:

type IdentityGenFunc = <T>(t: T) => T

Genel bir fonksiyondaki tip parametresi (T yukarıda) fonksiyon gerçekten çağrılana kadar belirtilmez; bu noktada çağıran kişi bunu belirtir (veya derleyici çağıran kişi adına bunu çıkarır). Bu, bir genel fonksiyon uygulamasının, fonksiyon çağıranın istediği T’nin herhangi bir olası belirtimiyle başa çıkabilmesi gerektiği anlamına gelir:

const idGenFunc: IdentityGenFunc = x => x;

const resultABC = idGenFunc("ABC"); // T inferred here as ABC
// const resultABC: "ABC"
const result123 = idGenFunc(123); // T inferred here as 123
// const result123: 123

Genel bir tip, genel tip parametresini (veya parametrelerini) tip bildirimi üzerinde tanımlar:

type IdentityGenType<T> = (t: T) => T

Genel bir tür üzerindeki tür parametresi (T yukarıda) o türde bir değere sahip olabilmeden önce belirtilmelidir. Eğer bir genel tür, tür parametresine atıfta bulunan bir çağrı imzasına veya metoda sahipse, o tür parametresi, çevreleyen genel tür belirtildiğinde sabitlenir, o metod çağrılmadan önce. Örneğin bir IdentityGenType<string>'ten söz ediyorsanız, çağrı imzasının yalnızca bir string ile başa çıkabilmesi gerekir. Bu yüzden bu fonksiyonu uygulamak için T'nin olası herhangi bir değeriyle ilgilenmenize gerek yoktur:

const idGenTypeString: IdentityGenType<string> = x => x + "!";

const resultABCagain = idGenTypeString("ABC"); // okay, "ABC" is assignable to string
// const resultABCagain: string
const result123Again = idGenTypeString(123); // error! 123 is not assignable to string

Bu iki jenerik tür birbiriyle ilişkilidir. IdentityGenFunc türü esasen her olası T için IdentityGenType<T> türünün bir "sonsuz kesişimi"dir. TypeScript tür sistemi bunu söyleyecek kadar ifade edici değildir:

// the following is not valid TS, but this is what you want to say
type IdentityGenFunc = forall T, IdentityGenType<T>; // not valid TS, error

ama bunu ödevlerle gözlemleyebilirsiniz:

// every IdGenFunc is also an IdGenType<number>
const idGenTypeNumber: IdentityGenType<number> = idGenFunc; // okay

Öte yandan, IdentityGenType<T> öğesini, bazı T türüyle örneklenmiş IdentityGenFunc ile aynı olarak düşünebilirsiniz. Yine, tür sistemi bunu ifade etmenize izin vermez:

// the following is not valid TS, but this is what you want to say
type GenType<T> = instantiate GenFunc with T; // not TS, error

TypeScript 4.7, türü bir jenerik fonksiyon olan bir değer üzerinde tür parametrelerini başlatmanıza izin verecek başlatma ifadelerini tanıtacak olmasına rağmen:

// if you instantiate a value of type IdGenFunc with Date, then you get an IdGenType<Date> 
const idGenTypeDate: IdentityGenType<Date> = idGenFunc<Date>; // okay for TS4.7+

Koda yönelik oyun alanı bağlantısı

Cevap #2

Benim deneyimime göre:

  1. type Type <T>=(t: T) => T için, bu bir fonksiyon gibidir, T'yi mantıklı hale getirmek için bir parametre vermeniz gerekir.
  2. type Type=<T> (t: T) => T için, bu sadece <T> (t: T) => T tipine başka bir isim vermek anlamına gelir ve burada T, t yerine verdiğiniz değere göre belirlenecek ve onu belirlemek için parametre veremezsiniz.

Genel bir fonksiyon için, örneğin const a=<T,> (t: T): T => t ,bir type a=<T> (t: T) => T tanımlayabilir ve sonra bunun için const a: a=<T,> (t: T): T => t yazabilirsiniz:

type a = <T> (t: T) => T
const a: a = <T,> (t: T): T => t

// same as const a: <T> (t: T) => T = <T,> (t: T): T => t

Tipi T, fonksiyonun kendisi tarafından belirlenir.

Eğer bunun T’nin sadece fonksiyonun kendisi değil de başka bir şeye karar vermesi gerekiyorsa, şöyle:

type aa <T> = { head: T, tail: (t: T) => T }

O zaman bir tür takma adı tanımladığınızda <T>, = işaretinin sol tarafında olmalıdır. Burada, T aynı zamanda tail tarafından, yani işlevin kendisi tarafından da belirlenebilir, ancak bu (t: T) => T türündeki işlevin parametresinin türü, head alanındaki değerin türüyle eşit olmalıdır. Nerede değiştirirseniz değiştirin, bunları eşit hale getirmelisiniz veya bu tür takma ad tanımı da değiştirilmelidir.


ör.

Şunları tanımlayabilirsiniz:

type Fn <T, R> = (x: T) => R ;
type Pipeyard <T> = <R,> (continuation: Fn<T, R>) => Pipeyard<R> ;

const Pipeyard = 
<T,> (head: T)
: Pipeyard<T> => 
    
    ( <R,> (continuation: Fn<T, R>)
    : Pipeyard<R> => 
        
        Pipeyard (continuation(head)) 
    
    ) as Pipeyard <T> ;

Bunun = işaretinin sol tarafında <T> ve sağ tarafında <R> vardır.

Bu define şöyle kullanılabilir:

Pipeyard (3) 
    (x => `x: ${x}`) 
    (x => "~ " + x) 
    (console.log); // "~ x: 3"

Türlerin türetilmesi otomatik! Çünkü bir sonraki <T> olan <R>, verdiğiniz fonksiyonun dönüş türü tarafından belirlenir.

(Ve bu kodlar pure-symbols/pure.closures deposundan agpl-3.0 lisansı altında 🙃)

İlgili makaleler