如何使用TypeScript的is类型谓词实现幂等(自扁平化)对象类型?
// 定义唯一标识,避免属性名冲突 const P_TAG = Symbol('P_TAG'); // 基础P类型定义 type PBase<A> = { [P_TAG]: true; mp: <B>(f: (a: A) => B) => P<B>; }; // 幂等解包类型:递归把嵌套的P展开,保证P<P<A>> = P<A> type UnwrapP<T> = T extends PBase<infer U> ? UnwrapP<U> : T; type P<A> = PBase<UnwrapP<A>>; // 类型守卫:判断是否是P类型,逻辑和判断条件匹配 const isP = <T>(x: unknown): x is P<T> => { return typeof x === 'object' && x !== null && P_TAG in x; }; // P构造函数 function P<A>(x: A): P<A> { // 运行时幂等:如果已经是P类型直接返回 if (isP(x)) return x as P<A>; // 非P类型包装 const wrapped = Object(x) as any; wrapped[P_TAG] = true; wrapped.mp = <B>(f: (a: UnwrapP<A>) => B) => P(f(wrapped.valueOf())); return wrapped as P<A>; } // 测试用例 const f = (a: number) => a + 1; const x = P(5); // 类型正确:P<number> const xx = P(P(5)); // 类型正确:P<number> 满足幂等 const a = P(5).mp(f); // 类型正确:P<number> const b = P(P(5)).mp(f).mp(f); // 链式调用正常,类型正确:P<number>
核心修正点
- 类型守卫逻辑修正:原代码的守卫把判断逻辑和返回的类型谓词写反了,携带标识属性的才是P类型,所以守卫返回值需要和判断条件匹配。
- 幂等类型实现:通过
UnwrapP递归解包嵌套的P类型,只要传入的是P实例,就提取内部的原始类型,保证P<P<number>>最终解析为P<number>,完全符合幂等要求。 - 运行时和类型对齐:构造函数先判断传入值是否是P实例,是就直接返回,运行时满足
Object(5) === Object(Object(5))的幂等特性,类型上也同步返回解包后的P类型。
内容的提问来源于stack exchange,提问作者Functor
相关产品推荐
相关产品推荐

