以下界面及功能,默认为
K
、V
、T
:
interface IBST<K = number, V = string> {
key: K;
value: V;
parent?: this;
left?: this;
right?: this;
}
function *walkInOrder<K = number, V = string, T extends IBST<K, V> = IBST<K, V>>(tree?: T): Generator<T> {
if (tree === undefined) return;
yield* walkInOrder(tree.left);
yield tree;
yield* walkInOrder(tree.right);
}
导致以下错误,其中
walkInOrder
递归地调用自身(正是对于表达式 walkInOrder(tree.left)
和 walkInOrder(tree.right)
):
类型“IBST
无关”不可分配给类型“T”。 'T' 可以用任意类型实例化,该类型可能与 'IBST '.ts(2322)
我对此感到惊讶,因为我认为这些默认值将用作无法从上下文推断 K 和 V 的类型时的后备,但现在在我看来,通用默认值不是后备,而是某种东西别的。一般而言,考虑这种情况和通用变量默认值的好方法是什么?
通话签名
<K, V, T extends IBST<K, V> = IBST<K, V>>(tree?: T) => Generator<T>
是相当有问题的,因为K
或
V
没有推理站点。当您调用该函数时,可以从
T
的参数推断出 tree
的类型(假设它已传入),但无法从中推断出 K
或 V
。您可能认为 T
的 constraint可以充当这样的推理站点,但 TypeScript 并不是这样工作的。请参阅 microsoft/TypeScript#7234。该函数的泛型类型参数太多。
因此,每当您调用该函数时,除非您手动指定类型参数,否则 K
和
V
的推理将失败,并且它们将回退到某些内容。如果
K
和 V
被赋予 default 类型参数,它们将回退到这些默认值。否则他们就会回到原来的束缚。由于没有显式约束,它们将退回到 unknown
的隐式约束。
那么,让我们看看那些递归调用:
鉴于
function* walkInOrder<K, V, T extends IBST<K, V> = IBST<K, V>>(
tree?: T): Generator<T> {
if (tree === undefined) return;
yield* walkInOrder(tree.left);
yield tree;
yield* walkInOrder(tree.right);
}
tree.left
和tree.right
的类型是T | undefined
,因此在这些调用中,内部调用的T
类型参数被推断为与外部调用的T
类型参数相同。 K
和 V
无法推断,因此它们回落到 unknown
。这就像打电话walkInOrder<unknown, unknown, T>(tree.left)
。因此 T
的约束是 IBST<unknown, unknown>
。无论 IBST<K, V>
和 K
是什么,它都是 V
的超类型,因此它会成功。
另一方面,鉴于
function* walkInOrder<K = number, V = string, T extends IBST<K, V> = IBST<K, V>>(
tree?: T): Generator<T> {
if (tree === undefined) return;
yield* walkInOrder(tree.left);
yield tree;
yield* walkInOrder(tree.right);
}
对于内部调用推断出相同的
T
,但现在 K
和 V
分别回落到 string
和 number
。这就像打电话walkInOrder<string, number, T>(tree. left)
。因此 T
的约束是 IBST<string, number>
。 *不*知道这是 IBST<K, V>
的超类型,所以它失败。一旦失败,T
就会回退到its约束,现在内部walkInOrder()
返回Generator<IBST<string, number>>
,(出于同样的原因)不知道它可以分配给Generator<T>
。
所以这就是正在发生的事情。我想说这里的问题与默认值无关,而与无法推断
K
和 V
的事实有关。如果你想做到这一点,你可以在内部调用中显式指定 K
和 V
,例如
function* walkInOrder<K = number, V = string, T extends IBST<K, V> = IBST<K, V>>(tree?: T): Generator<T> {
if (tree === undefined) return;
yield* walkInOrder<K, V, T>(tree.left);
yield tree;
yield* walkInOrder<K, V, T>(tree.right);
}
但是,当您尝试在通话外调用
walkInOrder()
时,您会遇到同样的问题,除非您手动指定 K
和 V
,并且不清楚 K
和 V
的实际用途是什么因为输出类型是 Generator<T>
,它根本不关心 K
和 V
。您也可以删除 K
和 V
并计算 T
中的所有内容,或者删除 T
并计算 K
和 V
中的所有内容。这个效果很好:
function* walkInOrder<K = number, V = string>(tree?: IBST<K, V>): Generator<IBST<K, V>> {
if (tree === undefined) return;
yield* walkInOrder(tree.left);
yield tree;
yield* walkInOrder(tree.right);
}