使用fp-ts在函数式编程中去除Either数组重复项的最优方案?
在fp-ts中去除Either类型数组重复项的最优方法
你的代码确实能实现去重效果,但有个核心问题:依赖了外部的checkList可变数组,这会让program变成不纯函数——多次调用时,checkList的残留状态会导致结果不一致,完全违背了函数式编程“无副作用、相同输入必返回相同输出”的核心原则。
下面给出两种更符合函数式风格的纯函数实现,分别对应不同的需求场景:
场景1:重复的成功项转为INVALID Left(和你的原逻辑对齐)
这个方案会保留所有原数组项,但把重复的Right值转为type: "INVALID"的Left,同时全程保持纯函数特性:
import { either as E, array as A, set as S } from "fp-ts"; import { flow, pipe } from "fp-ts/lib/function"; interface IItem { type: "VALID" | "INVALID"; value: string; } // 构建模拟数据 const buildItem = (value?: string): E.Either<unknown, string> => value != null ? E.right(value) : E.left({ type: "INVALID", value: "" }); const items = [ buildItem("aa"), buildItem("ab"), buildItem(), buildItem("ac"), buildItem("ab"), buildItem("ac"), buildItem(), buildItem("aa") ]; export const program = flow( () => items, // 先把数组拆分为Right成功项和Left失败项 A.partition(E.isRight), ({ right: successes, left: failures }) => { // 提取所有成功项的value,存入Set实现去重 const uniqueValues = S.fromSet(new Set(successes.map(E.right.getOrElse(() => "")))); // 遍历成功项,重复的转为INVALID Left,否则转为VALID Right const processedSuccesses = successes.map(success => pipe( success, E.chain(value => S.member(uniqueValues)(value) ? E.right({ type: "VALID", value } as IItem) : E.left({ type: "INVALID", value } as IItem) ) ) ); // 合并处理后的成功项和原失败项,返回最终数组 return [...processedSuccesses, ...failures]; } );
场景2:直接过滤重复的成功项(更简洁的常见需求)
如果你的需求是只保留首次出现的成功项,同时保留所有失败项,可以用reduce维护一个纯状态(每次返回新的集合和数组,不修改原数据):
export const programFilterDuplicates = flow( () => items, A.reduce( // 初始状态:空的已见集合 + 空结果数组 { seen: new Set<string>(), result: [] as E.Either<unknown, IItem>[] }, ({ seen, result }, item) => pipe( item, E.fold( // Left项直接加入结果,状态不变 leftVal => ({ seen, result: [...result, E.left(leftVal)] }), // Right项检查是否已存在 value => { if (!seen.has(value)) { // 未见过则更新状态:新的已见集合 + 加入VALID Right的结果数组 return { seen: new Set([...seen, value]), result: [...result, E.right({ type: "VALID", value } as IItem)] }; } // 已见过则跳过,状态不变 return { seen, result }; } ) ) ), // 提取最终结果数组 ({ result }) => result );
最优实践总结
- 绝对避免外部可变状态:这是函数式编程的底线,所有状态都应该在函数内部以纯的方式传递和更新
- 优先使用fp-ts工具函数:
A.partition、S.fromSet、E.fold这些工具函数都是经过验证的,能帮你减少手动编写的重复逻辑,同时保持代码的函数式风格 - 根据需求选择处理策略:如果需要保留所有项并标记重复项,用场景1;如果只需要保留唯一项,场景2更简洁
内容的提问来源于stack exchange,提问作者Amin Paks
相关产品推荐
相关产品推荐

