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

如何为同类型嵌套配对参数函数添加TS类型?TypeScript有无类型类等价特性?

解决方案:为递归配对类型的plus函数添加类型定义

一、正确的plus函数类型约束

原方案的问题在于允许T为联合类型(如number | [number, number]),导致非法调用无法被拦截。我们可以通过非联合类型约束+递归类型断言解决这个问题:

// 定义递归的嵌套数值配对类型
type NestedNumberPair = number | [NestedNumberPair, NestedNumberPair];

// 辅助类型:判断类型是否为联合类型
type IsUnion<T, U = T> = T extends U ? [U] extends [T] ? false : true : false;

// 辅助类型:仅允许非联合类型,拦截联合类型参数
type NotUnion<T> = IsUnion<T> extends true ? never : T;

function plus<T extends NestedNumberPair>(a: NotUnion<T>, b: NotUnion<T>): T {
  if (Array.isArray(a)) {
    // 递归调用后断言返回类型为T(编译器无法自动递归推断)
    return [plus(a[0], b[0]), plus(a[1], b[1])] as T;
  } else {
    // 断言数值相加结果为T
    return (a + b) as T;
  }
}

// 合法调用(类型检查通过)
plus(1, 2); // 返回类型:number
plus([1, 2], [3, 4]); // 返回类型:[number, number]
plus([1, [2, 3]], [10, [10, 10]]); // 返回类型:[number, [number, number]]

// 非法调用(类型检查报错)
plus(1, [10, 10]); // 参数类型不匹配
plus([0, [1, 1]], [[0, 0], 1]); // 参数结构不匹配

核心逻辑

  • NestedNumberPair严格定义了允许的递归结构:要么是number,要么是两个同结构元素组成的数组。
  • NotUnion<T>通过类型判断拦截联合类型,确保参数必须是单一的嵌套结构,而非多种结构的混合。
  • 递归调用后的类型断言是必要的:TypeScript编译器无法自动推断递归返回值与T的匹配关系,需要手动断言确认。

二、TypeScript中模拟类型类(Type Class)的特性

TypeScript没有原生的类型类支持,但可以通过接口+泛型+递归条件类型模拟类型类的核心能力——即ad-hoc多态(根据类型自动选择对应实现)。

方式1:基于接口的类型类模拟

定义通用的可相加接口,为不同类型实现该接口:

// 定义"可相加"类型类接口
interface Addable<T> {
  add(a: T, b: T): T;
}

// 为number类型实现Addable
const NumberAddable: Addable<number> = {
  add(a, b) {
    return a + b;
  }
};

// 递归为嵌套配对类型实现Addable
type NestedPair<T> = T | [NestedPair<T>, NestedPair<T>];

function getAddable<T extends number>(): Addable<NestedPair<T>> {
  return {
    add(a, b) {
      if (Array.isArray(a)) {
        const pairAddable = getAddable<T>();
        return [pairAddable.add(a[0], b[0]), pairAddable.add(a[1], b[1])] as NestedPair<T>;
      } else {
        return NumberAddable.add(a, b) as NestedPair<T>;
      }
    }
  };
}

// 获取类型安全的plus函数
const plus = getAddable<number>().add;

方式2:基于条件类型的分发

利用TypeScript的条件类型分发特性,直接在函数类型中处理不同层级的嵌套结构:

type NestedNumberPair = number | [NestedNumberPair, NestedNumberPair];

// 递归计算相加后的返回类型
type AddResult<T extends NestedNumberPair> = 
  T extends number ? number :
  T extends [infer A, infer B] ? [AddResult<A>, AddResult<B>] :
  never;

function plus<T extends NestedNumberPair>(a: T, b: T): AddResult<T> {
  if (Array.isArray(a)) {
    return [plus(a[0], b[0]), plus(a[1], b[1])] as AddResult<T>;
  } else {
    return (a + b) as AddResult<T>;
  }
}

说明

这两种方式都实现了类似类型类的效果:针对不同类型(基础类型、嵌套结构)提供对应的相加逻辑,同时保证类型安全。但相比Haskell等原生支持类型类的语言,TypeScript的模拟需要更多手动编写的代码,无法自动推导实例。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 20:36:23