如何从F#可区分联合类型列表中通用选取指定成员?
通用提取F#可区分联合列表中特定case的方案
你尝试的泛型方案行不通,因为F#可区分联合(DU)的各个case并不是Buff的子类型——它们本质上都是Buff类型的实例,只是构造形式不同,所以:? 'T这种类型测试无法区分不同的case。
下面是几种可行的通用解决方案:
方案1:直接传入匹配逻辑
无需为每个case单独定义函数,调用List.tryPick时直接传入对应的模式匹配lambda即可,避免重复代码:
// 提取列表中第一个Block的值 xs |> List.tryPick (function Block v -> Some v | _ -> None) // 提取列表中第一个Thorns的值 xs |> List.tryPick (function Thorns v -> Some v | _ -> None)
方案2:封装通用提取函数
如果想进一步复用逻辑,可以封装一个高阶函数,让调用者只需指定匹配提取规则:
let tryPickCase extractor = List.tryPick extractor // 调用方式和方案1一致 xs |> tryPickCase (function Block v -> Some v | _ -> None) xs |> tryPickCase (function Thorns v -> Some v | _ -> None)
方案3:生成对应case的提取函数
如果希望保留类似tryPickBlock的命名函数调用方式,但只写一次生成逻辑,可以创建一个提取器生成函数:
let createTryPickCase matcher = List.tryPick (fun buff -> match buff with | matcher v -> Some v | _ -> None) // 生成对应case的提取函数 let tryPickBlock = createTryPickCase (function Block v -> v | _ -> failwith "") let tryPickThorns = createTryPickCase (function Thorns v -> v | _ -> failwith "") // 调用方式和你原来的写法一致 xs |> tryPickBlock // 返回 Some 5 xs |> tryPickThorns // 返回 Some 3
注:这里的failwith永远不会触发,因为matcher只会在匹配到对应case时返回值,不会进入错误分支。
不推荐的反射方案(动态提取)
如果需要完全通用但不介意失去静态类型安全性,可以用F#反射实现动态提取,但这种方式无法区分同类型值的不同case(比如Block和Thorns都是int类型时无法精准指定),仅适合特殊场景:
open FSharp.Reflection let tryPickUnionCase<'Value> (xs: Buff list) : 'Value option = xs |> List.tryPick (fun buff -> let caseInfo, args = FSharpValue.GetUnionFields(buff, typeof<Buff>) if args.Length = 1 && args.[0].GetType() = typeof<'Value> then Some (args.[0] :?> 'Value) else None) // 调用时指定值类型(局限性:会返回第一个匹配的同类型值) xs |> tryPickUnionCase<int> // 返回 Some 5
内容的提问来源于stack exchange,提问作者Sven Bardos
相关产品推荐
相关产品推荐

