在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:
- 支持更多类型:如果需要支持字符串、数字这类原始类型,只需要在append里加对应的判断即可。比如字符串可以直接
return (x + y) as unknown as A,数字加法同理(要注意半群的结合律,加法是满足的,乘法也可以,按需选择)。 - 自定义对象合并逻辑:如果需要深合并而不是浅合并,把对象分支的
{...x, ...y}换成你自己的深合并函数就行(比如实现一个递归合并,或者用成熟的工具函数)。 - 兼容fp-ts风格:如果某些场景需要自定义特定的Semigroup(比如对象的合并规则和通用版不一样),完全可以单独写实例,就像fp-ts那样,比如:
// 自定义深合并的User Semigroup const deepMergeUserSemigroup: Semigroup<User> = { append: (x, y) => deepMerge(x, y) // 假设你有一个deepMerge函数 };
这种方案既满足了通用需求,又给自定义逻辑留了口子,算是平衡了简洁性和灵活性。
内容的提问来源于stack exchange,提问作者dagda1
相关产品推荐
相关产品推荐

