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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 12:27:33