如何实现可指定区分联合类型分支的泛型筛选函数?
泛型筛选区分联合类型列表的实现方案
首先回顾你的基础代码:
定义区分联合类型
type MyUnionType = | TInt of int | TString of string | TBool of bool
待筛选的联合类型列表
let myUnionList = [ TInt 1 TString "1" TInt 3 TBool false TBool false TInt 6 TString "one" TString "two" TBool true TInt 9 ]
原筛选方式
let filtered = myUnionList |> List.choose (fun i -> match i with | TString s -> Some s | _ -> None) // filtered = [ "1"; "one"; "two" ]
核心结论
可以编写泛型函数实现需求,区分联合类型没有限制,关键是不能直接传递联合分支(分支不是一等公民),但可以传递「提取分支值的函数」作为参数。
具体实现方式
1. 传入提取函数(最直接的函数式方案)
定义一个泛型筛选函数,接收一个从联合类型到可选值的提取逻辑:
let filterUnion (extract: MyUnionType -> 'a option) list = list |> List.choose extract
使用时只需传入对应分支的提取函数:
// 提取TString分支的函数 let extractString = function | TString s -> Some s | _ -> None let filteredStrings = filterUnion extractString myUnionList // 结果:[ "1"; "one"; "two" ] // 提取TInt分支的函数 let extractInt = function | TInt i -> Some i | _ -> None let filteredInts = filterUnion extractInt myUnionList // 结果:[1; 3; 6; 9]
2. 活动模式封装(更优雅的语法)
可以为每个分支定义活动模式,再结合泛型函数使用:
// 为各分支定义提取用的活动模式 let (|ExtractTInt|_|) = function | TInt i -> Some i | _ -> None let (|ExtractTString|_|) = function | TString s -> Some s | _ -> None let (|ExtractTBool|_|) = function | TBool b -> Some b | _ -> None // 泛型筛选函数 let filterWithPattern extract list = list |> List.choose extract // 使用示例 let filteredStrings = filterWithPattern (fun x -> match x with | ExtractTString s -> Some s | _ -> None) myUnionList
3. 反射实现(不推荐)
如果需要动态传入分支名称(如字符串"TString"),可以用反射,但此方案性能差且类型不安全,仅作参考:
open System.Reflection let filterByUnionCaseName<'a> (caseName: string) (list: MyUnionType list) = let unionType = typeof<MyUnionType> let caseInfo = unionType.GetTypeInfo().DeclaredNestedTypes |> Seq.find (fun t -> t.Name = caseName) let extractMethod = caseInfo.GetMethod("op_Implicit", BindingFlags.Public ||| BindingFlags.Static) list |> List.choose (fun u -> let currentCase = FSharpValue.GetUnionFields(u, unionType) |> fst if currentCase.Name = caseName then Some (extractMethod.Invoke(null, [|u|]) :?> 'a) else None) // 使用示例 let filteredStrings = filterByUnionCaseName<string> "TString" myUnionList
内容的提问来源于stack exchange,提问作者rasx
相关产品推荐
相关产品推荐

