从属通用约束变量的通用默认触发错误

问题描述 投票:0回答:1

以下界面及功能,默认为

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 的类型时的后备,但现在在我看来,通用默认值不是后备,而是某种东西别的。一般而言,考虑这种情况和通用变量默认值的好方法是什么?

typescript typescript-generics
1个回答
0
投票

通话签名

<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);
}

Playground 代码链接

© www.soinside.com 2019 - 2024. All rights reserved.