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

在TypeScript中实现通用Semigroup Append方法的技术问询

嘿,这个问题我之前也琢磨过——既要保住Semigroup的抽象性,又不想像fp-ts那样给每种类型单独写append实现,确实有点挠头。不过咱们可以结合TypeScript的类型守卫和泛型,搞一个既通用又类型安全的方案,同时还能兼顾数组和对象的不同追加逻辑。

先把咱们的核心接口摆出来,和你定义的一致:

export interface Semigroup<A> {
  append: (x: A, y: A) => A;
}

接下来的关键是,怎么让append方法能自动识别数组和对象,然后执行对应的逻辑?这里咱们需要两个类型守卫函数,帮TypeScript判断当前处理的是什么类型:

// 判断是否为数组
function isArray<T>(val: unknown): val is T[] {
  return Array.isArray(val);
}

// 判断是否为普通对象(排除null、数组、函数等特殊对象)
function isPlainObject<T extends Record<string, unknown>>(val: unknown): val is T {
  return (
    typeof val === 'object' &&
    val !== null &&
    !Array.isArray(val) &&
    Object.prototype.toString.call(val) === '[object Object]'
  );
}

有了类型守卫,咱们就可以写一个通用的makeSemigroup函数,它能根据输入的类型自动返回对应的Semigroup实例:

export function makeSemigroup<A>(): Semigroup<A> {
  return {
    append: (x: A, y: A): A => {
      // 数组逻辑:拼接两个数组
      if (isArray(x) && isArray(y)) {
        return [...x, ...y] as unknown as A;
      }

      // 对象逻辑:浅合并,后面的属性覆盖前面的
      if (isPlainObject(x) && isPlainObject(y)) {
        return { ...x, ...y } as unknown as A;
      }

      // 其他类型可以在这里扩展,比如字符串拼接、数字加法等
      throw new Error(`Unsupported type for Semigroup append: ${typeof x}`);
    }
  };
}

用起来也很简单,看几个示例:

// 数组类型的Semigroup
const numberArraySemigroup = makeSemigroup<number[]>();
console.log(numberArraySemigroup.append([1, 2], [3, 4])); // 输出: [1, 2, 3, 4]

// 对象类型的Semigroup
type User = { name: string; age: number };
const userSemigroup = makeSemigroup<User>();
console.log(userSemigroup.append({ name: 'Alice', age: 30 }, { age: 31 })); // 输出: { name: 'Alice', age: 31 }

额外扩展小 tips:

  1. 支持更多类型:如果需要支持字符串、数字这类原始类型,只需要在append里加对应的判断即可。比如字符串可以直接return (x + y) as unknown as A,数字加法同理(要注意半群的结合律,加法是满足的,乘法也可以,按需选择)。
  2. 自定义对象合并逻辑:如果需要深合并而不是浅合并,把对象分支的{...x, ...y}换成你自己的深合并函数就行(比如实现一个递归合并,或者用成熟的工具函数)。
  3. 兼容fp-ts风格:如果某些场景需要自定义特定的Semigroup(比如对象的合并规则和通用版不一样),完全可以单独写实例,就像fp-ts那样,比如:
// 自定义深合并的User Semigroup
const deepMergeUserSemigroup: Semigroup<User> = {
  append: (x, y) => deepMerge(x, y) // 假设你有一个deepMerge函数
};

这种方案既满足了通用需求,又给自定义逻辑留了口子,算是平衡了简洁性和灵活性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 09:25:32