[英]How to tell TypeScript that two generic types are the same?
考慮以下重載的 function。
function scan<A>(this: A[], f: (a: A, x: A) => A): A[];
function scan<A, B>(this: A[], f: (a: B, x: A) => B, init?: B): B[] {
if (init === undefined) {
const result = [this[0]];
for (let i = 1; i < this.length; i++) {
result.push(f(result[i - 1], this[i]));
}
return result;
}
const result = [init];
for (let i = 0; i < this.length; i++) {
result.push(f(result[i], this[i]));
}
return result;
}
請注意,當未提供init
時,泛型類型B
應與A
相同。 我該如何告訴 TypeScript? 目前,TypeScript 抱怨A
不能分配給B
,反之亦然。
重載的 function具有一組調用簽名聲明,這些聲明確定如何調用 function,並且(假設 function 已實現而不是僅聲明單個實現。 實現簽名是不可調用的。
在您的示例代碼中,您有一個調用簽名
// call signature
function scan<A>(this: A[], f: (a: A, x: A) => A): A[];
和一個實現
// implementation
function scan<A, B>(this: A[], f: (a: B, x: A) => B, init?: B): B[] {
/* snip */
}
但這似乎不是你想要的。 您真的希望這些簽名都是調用簽名,如下所示:
// call signatutes
function scan<A>(this: A[], f: (a: A, x: A) => A): A[];
function scan<A, B>(this: A[], f: (a: B, x: A) => B, init?: B): B[];
// implementation
function scan(...) {
所以問題是:實現簽名應該是什么?
TypeScript 的編譯器無法通過分別檢查每個調用簽名來檢查實現。 在microsoft/TypeScript#13235有一個建議這樣做,但由於過於復雜而無法實現,因此被關閉。 相反,編譯器所做的是確保實現簽名參數可以處理來自每個調用簽名的參數,並確保實現簽名返回類型可以處理來自每個調用簽名的返回返回類型。 也就是說,返回類型可以是所有調用簽名的返回類型的並集。 這不是類型安全的(因為您可能會為特定的調用簽名返回錯誤的類型),但很方便。
不管好壞,這種松散的檢查是 TypeScript 的重載實現的工作方式。 所以在編寫重載函數時需要小心。
無論如何,這意味着實現需要是這樣的:
// implementation signature
function scan<A, B>(this: A[], f: (a: B | A, x: A) => A, init?: B | A) {
if (init === undefined) {
const result = [this[0]];
for (let i = 1; i < this.length; i++) {
result.push(f(result[i - 1], this[i]));
}
return result;
}
const result = [init];
for (let i = 0; i < this.length; i++) {
result.push(f(result[i], this[i]));
}
return result;
}
它無論如何都不是完美的,但如果我們想把這兩個獨立的行為放到一個重載的 function 中,它可能是我們能得到的最好的。
我能夠通過削弱實現簽名來進行類型檢查。 經驗教訓,實現簽名必須始終是重載簽名的組合。
const SCAN_EMPTY_NO_INIT = 'Scan of empty array with no initial value';
type Reducer<A, B> = (acc: B, a: A) => B;
function scan<A>(this: A[], f: Reducer<A, A>): A[];
function scan<A, B>(this: A[], f: Reducer<A, B>, init: B): B[];
function scan<A, B>(this: A[], f: Reducer<A, A | B>, init?: B): (A | B)[] {
const { length } = this;
let i = 0, result = [typeof init === 'undefined' ? this[i++] : init];
if (length < i) throw new TypeError(SCAN_EMPTY_NO_INIT);
const j = i;
while (i < length) {
result.push(f(result[i - j], this[i]));
i++;
}
return result;
}
請注意,不能使用實現簽名調用 function。 因此,您不能以意想不到的方式使用此 function 來創建A
或B
的數組。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.