F#如何实现可区分联合列表按分支类型去重
可区分联合类型按分支去重实现方法
问题说明
需求为构造可区分联合类型的列表,约束为同一联合分支仅允许存在一个元素,去重判定与分支关联的底层数据无关。
示例代码:
type Car = | Honda | Tesla of string let carList = [Honda; Tesla("Tesla"); Tesla(""); Tesla("Tesla")] // 预期去重结果 let correctCarList = [Honda; Tesla ("Tesla")]
该规则下,只要元素属于Tesla分支,无论携带的字符串内容是什么,都判定为重复项,列表仅保留第一个出现的该分支元素即可。
默认Set结构无法满足需求:Set判定重复的逻辑是分支类型+关联底层数据完全相等,上述示例用Set去重只会移除完全重复的Tesla("Tesla"),会同时保留Tesla("Tesla")和Tesla("")两个同分支元素,不符合要求。
可行实现方案
完全可以实现仅按可区分联合分支类型的去重逻辑,没有语法层面的限制,常见实现方式有两种:
方案1:反射提取分支标签(通用方案)
通过FSharp.Reflection模块提供的API,可以直接获取任意可区分联合实例对应的分支标签,不需要为每个类型单独写匹配逻辑,适配所有可区分联合类型:
open FSharp.Reflection let distinctByUnionCase (list: 'T list) = if not (FSharpType.IsUnion(typeof<'T>)) then invalidArg "list" "入参必须为可区分联合类型列表" list |> List.indexed |> List.map (fun (idx, item) -> let unionCase = FSharpValue.GetUnionFields(item, typeof<'T>) |> fst (unionCase.Name, idx, item)) |> List.distinctBy (fun (caseName, _, _) -> caseName) |> List.sortBy (fun (_, idx, _) -> idx) |> List.map (fun (_, _, item) -> item) // 测试验证 let carList = [Honda; Tesla("Tesla"); Tesla(""); Tesla("Tesla")] let correctCarList = distinctByUnionCase carList // 运行结果为 [Honda; Tesla "Tesla"],符合预期
方案2:手动映射分支标识(高性能方案)
如果不想引入反射依赖,或者对运行性能有更高要求,可以手动编写匹配逻辑,将每个分支映射为独立的标识值做去重键,适合分支数量固定且较少的场景:
// 为Car类型编写分支标识映射 let getCarCaseKey = function | Honda -> 0 | Tesla _ -> 1 let correctCarList = carList |> List.distinctBy getCarCaseKey
两种方案都可以实现「同分支仅保留一个元素、忽略分支携带数据差异」的约束。
内容的提问来源于stack exchange,提问作者JavaHava
相关产品推荐
相关产品推荐

