我正在使用 TypeScript 的通用约束对递归二叉搜索树 (BST) 数据类型进行建模。要定义递归泛型变量
T
的默认值,而不在默认值中使用 T
(不允许),我有一个约束的共同递归“基本情况”,如下所示:
// Bounded polymorphism using recursive generic constraints.
type IBSTBoRecDflt<K, V> = IBSTBoRec<K, V, IBSTBoRecDflt<K, V>>;
interface IBSTBoRec<K, V, T extends IBSTBoRec<K, V, T> = IBSTBoRecDflt<K, V>> {
key: K;
value: V;
left?: T;
right?: T;
}
type BSTBoRecDeflt<K, V> = BSTBoRec<K, V, BSTBoRecDeflt<K, V>>;
class BSTBoRec<K, V, T extends BSTBoRec<K, V, T> = BSTBoRecDeflt<K, V>> implements IBSTBoRec<K, V, T> {
key: K;
value: V;
left?: T;
right?: T;
constructor(key: K, value: V, left?: T, right?: T) {
this.key = key;
this.value = value;
this.left = left;
this.right = right;
}
}
const t = new BSTBoRec(5, 'e', undefined, new BSTBoRec(8, 'h'));
问题在于,当左分支丢失时,右分支
(8, h)
的类型似乎会被错误地推断出来。最后一行的 new BSTBoRec(8, 'h')
会抛出以下错误:
Argument of type 'BSTBoRec<number, string, BSTBoRec<number, string, undefined>>' is not assignable to parameter of type
'BSTBoRec<number, string, BSTBoRec<number, string, BSTBoRec<number, string, undefined>>>'.
Type 'BSTBoRec<number, string, undefined>' is not assignable to type
'BSTBoRec<number, string, BSTBoRec<number, string, undefined>>'.
Type 'undefined' is not assignable to type 'BSTBoRec<number, string, undefined>'.
如果我手动指定
K
分支的 V
、(8, h)
类型,错误就会消失:
const t = new BSTBoRec(5, 'e', undefined, new BSTBoRec<number, string>(8, 'h'));
为什么当省略左分支时,右分支的类型推断不正确?
这是一个带有代码和错误的playground。
我无法准确地说为什么推理失败,因为我还没有逐步完成编译器代码。我在 GitHub 中发现的最接近的问题是 microsoft/TypeScript#45286,其中推断出无效的 generic 类型参数,即使它无法满足 constraint。这也发生在这里;看起来 T
的第一个推理候选项未定义(这是
left
的类型),这会影响
new BSTBoRec(8, 'h')
的 上下文类型以包含
BSTBoRec<number, string, undefined>
,这是不应该发生的。理想情况下,推理在这里会“失败”,并且“
T
”会回落到约束条件,你只会得到“BTSBoRec<number, string>
”,但显然这不会发生。我想说这是一个 TypeScript 错误或设计限制,但谁知道它是否会得到解决。如果我想解决这个问题,我可能会通过更改
T
constructor(key: K, value: V, left?: BSTBoRec<K, V, T>, right?: BSTBoRec<K, V, T>) {
this.key = key;
this.value = value;
this.left = left as T;
this.right = right as T;
}]
这会降低T
的推理优先级,足以使其工作(除非您需要类型断言
将其分配给
T
,因为从技术上讲它可能无法分配):
const t1 = new BSTBoRec(5, 'e', new BSTBoRec(8, 'h'), undefined);
// const t1: BSTBoRec<number, string, BSTBoRecDeflt<number, string>>
const t2 = new BSTBoRec(5, 'e', undefined, new BSTBoRec(8, 'h'));
// const t2: BSTBoRec<number, string, BSTBoRecDeflt<number, string>>
或者您可以通过使用 tuple 类型的union
作为剩余参数来使构造函数重载或等效于重载:
constructor(key: K, value: V, ...[left, right]: [undefined, T] | [T?, T?]) {
this.key = key;
this.value = value;
this.left = left as T;
this.right = right as T;
}
undefined
因为 left
更喜欢
right
作为推断
T
的地方:
const t1 = new BSTBoRec(5, 'e', new BSTBoRec(8, 'h'), undefined);
// const t1: BSTBoRec<number, string, BSTBoRec<number, string,
// BSTBoRec<number, string, BSTBoRecDeflt<number, string>> | undefined>> ???!
const t2 = new BSTBoRec(5, 'e', undefined, new BSTBoRec(8, 'h'));
// const t2: BSTBoRec<number, string, BSTBoRec<number, string, BSTBoRecDeflt<number, string>>>
但这仍然有一个奇怪的 undefined
类型,不应该在那里,所以我可能更喜欢顶部的那个。总而言之,我想说,面对复杂的递归泛型类型参数默认值