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

如何在值级函数中使用AllCompatible避免Tuple类型信息丢失?

泛型链表与兼容类型验证的类型级函数问题

泛型链表类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。下面的函数能正确识别所有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>, ...>>>),为此定义了AllCompatible:

type Tuple<F, S> = [F, S];
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,避免每次取出的head元素类型退化为Tuple<any, any>?需要如何修改AllCompatible或为消费函数添加类型注解来保留精确类型信息?


解决方案

要保留AllCompatible链表中每个元素的精确类型,需要让AllCompatible不仅做类型校验,还要在递归过程中传递并保留节点间的类型关联,而非仅返回never或模糊的兼容类型。

修改AllCompatible类型

给AllCompatible添加泛型参数,显式跟踪链表节点的结束类型,从而在递归中传递类型约束:

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

// 新增End泛型参数,跟踪链表最后一个Tuple的第二个元素类型
type AllCompatible<C, End = never> = 
    C extends Cons<Tuple<infer Start, infer CurrentEnd>, infer Tail>
        ? Tail extends undefined
            ? End extends never 
                ? C // 单节点链表直接返回原类型
                : CurrentEnd extends End ? C : never // 验证单节点是否匹配指定End
            : Tail extends Cons<Tuple<CurrentEnd, infer NextEnd>, infer RestTail>
                ? Cons<Tuple<Start, CurrentEnd>, AllCompatible<Tail, End extends never ? NextEnd : End>>
                : never
        : never;

编写消费函数

基于修改后的AllCompatible,可以编写保留精确类型的消费函数:

function processCompatibleList<
    T extends Cons<Tuple<any, any>, Cons<any, any> | undefined>
>(list: AllCompatible<T>) {
    let current: typeof list | undefined = list;
    while (current) {
        const [head, rest] = current.split();
        // head类型会被精确推断为对应Tuple<X, Y>,而非Tuple<any, any>
        console.log(`当前节点:起始值 ${head[0]},结束值 ${head[1]}`);
        current = rest;
    }
}

// 测试示例:完全兼容的链表
const compatibleList = Cons.cons([3, 4] as const)
    .cons([2, 3] as const)
    .cons([1, 2] as const);

processCompatibleList(compatibleList); // 正常执行,每个head类型精确

修改原理

修改后的AllCompatible通过End泛型参数在递归过程中传递类型约束,TypeScript可以沿着链表的递归结构,逐步推断出每个节点的Tuple类型,避免退化为any。同时它依然保留校验功能:若链表不满足首尾兼容,AllCompatible<T>会返回never,调用消费函数时会触发类型错误。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 20:04:56