You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript中如何在值级函数使用AllCompatible并避免类型退化?

问题与解答

背景:泛型链表类Cons

我们有一个泛型类Cons,用于实现类Lisp风格的递归链表:

class Cons<Head, Rest extends Cons<any, any> | undefined> {
    constructor(public head: Head, public rest: Rest) {}

    static cons: <Item>(item: Item) => Cons<Item, undefined> =
        (item) => new Cons(item, undefined)

    cons: <Item>(item: Item) => Cons<Item, this> =
        (item) => new Cons(item, this)

    split: () => [Head, Rest] =
        () => [this.head, this.rest]
}

该链表支持异构元素,且元素类型会被精确体现在Cons实例的类型中,示例如下(添加冗余类型注解以增强可读性):

const c1: Cons<1, Cons<2, Cons<3, undefined>>>
    = Cons.cons(3 as const).cons(2 as const).cons(1 as const)

类型级函数:AllSameType

我们可以用类型级函数(即一种泛型类型,符合断言时返回原类型,否则返回never)对链表类型进行约束。比如下面这个递归类型级函数,用于断言链表所有元素类型相同:

type AllSameType<C> = C extends Cons<infer Head, infer Rest>
    ? Rest extends Cons<Head, infer _>
        ? Cons<Head, AllSameType<Rest>>
        : Rest extends undefined
            ? C
            : never
    : never

测试表明,AllSameType<Cons<1, Cons<1, Cons<1, undefined>>>>会返回原类型,而AllSameType<Cons<3, Cons<2, Cons<1, undefined>>>>则返回never。下面的reduce函数可以正确识别所有head的类型都是泛型变量Head:

function reduce<Head, Rest extends Cons<any, any>>(cons: AllSameType<Cons<Head, Rest>>) {
    let heads: Head[] = [];
    let [head, rest] = cons.split()
    heads.push(head)
    while (rest !== undefined) {
        [head, rest] = rest.split()
        heads.push(head)
    }
}

多米诺兼容链表的类型级函数:AllCompatible

假设有一个由Tuple<A, B>元组组成的链表,我们需要定义类似多米诺骨牌、首尾元素兼容的链表类型:Cons<Tuple<A, B>, Cons<Tuple<B, C>, Cons<Tuple<C, D>, ...>>>,对应的类型级函数如下:

type Tuple<A, B> = [A, B];
type AllCompatible<C> = C extends Cons<infer Head, infer Tail>
    ? Head extends Tuple<infer A, infer B>
        ? Tail extends undefined
            ? Cons<Tuple<A, B>, undefined>
            : Tail extends Cons<Tuple<B, infer _1>, infer _2>
                ? Cons<Tuple<A, B>, AllCompatible<Tail>>
                : never
        : never
    : never

技术问题

能否在值级函数中使用AllCompatible类型,同时避免依次取出的头部元素类型退化为Tuple<any, any>?该如何为这类函数的参数进行TypeScript类型标注?


解答

可以实现该需求,核心是通过泛型参数捕获链表的完整兼容类型结构,而非直接将AllCompatible作为参数类型。具体实现如下:

1. 优化AllCompatible类型(可选,增强严谨性)

先明确Tuple定义,同时让AllCompatible更精准地递归约束链状兼容关系:

type Tuple<F, T> = [F, T];

type AllCompatible<C> = 
  C extends Cons<Tuple<infer F, infer T>, infer Rest>
    ? Rest extends undefined
      ? C
      : Rest extends AllCompatible<Cons<Tuple<T, infer _>, infer __>>
        ? C
        : never
    : never;

2. 为值级函数标注泛型参数

通过泛型捕获链表的起始与后续兼容类型链,同时用AllCompatible做约束,让TypeScript保留每个节点的精确类型:

示例:遍历兼容链表并收集节点

function traverseCompatible<F, T, Rest extends Cons<any, any> | undefined>(
  cons: AllCompatible<Cons<Tuple<F, T>, Rest>>
): Tuple<any, any>[] {
  const nodes: Tuple<any, any>[] = [];
  let current: typeof cons = cons;
  
  while (true) {
    const [head, rest] = current.split();
    // head类型会被精确推断为Tuple<F, T>,后续节点类型依次匹配
    nodes.push(head);
    if (!rest) break;
    // 类型断言确保rest符合AllCompatible约束,避免类型退化
    current = rest as AllCompatible<typeof rest>;
  }
  
  return nodes;
}

进阶:捕获完整链类型以增强返回值精度

若需要让函数返回类型也体现链的兼容关系,可使用递归泛型捕获整个结构:

type CompatibleChain<F, T, Rest = undefined> = 
  Rest extends CompatibleChain<T, infer NT, infer NRest>
    ? Cons<Tuple<F, T>, Rest>
    : Cons<Tuple<F, T>, undefined>;

function traverseExact<F, T, Rest extends CompatibleChain<T, any> | undefined>(
  cons: CompatibleChain<F, T, Rest>
): Tuple<any, any>[] {
  const nodes: Tuple<any, any>[] = [];
  let current: typeof cons = cons;
  
  while (true) {
    const [head, rest] = current.split();
    nodes.push(head);
    if (!rest) break;
    current = rest as CompatibleChain<T, any, typeof rest>;
  }
  
  return nodes;
}

3. 测试验证

创建符合兼容要求的链表,函数可正确推断每个节点的精确类型:

// 创建多米诺兼容链表
const domino = Cons.cons([3, 4] as const)
  .cons([2, 3] as const)
  .cons([1, 2] as const);

// 调用traverseCompatible,head类型不会退化为Tuple<any, any>
traverseCompatible(domino);
// 每个节点类型分别为Tuple<1,2>、Tuple<2,3>、Tuple<3,4>

内容的提问来源于stack exchange,提问作者aas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 12:59:55