F# 实现支持两种Store类型列表的无额外DU泛型函数
F# 兼容两种相似记录类型的列表处理函数实现
问题描述
定义了如下两种记录类型:
type MedicalConditionStore = { MedicalCondition: MedicalCondition LastMod: UTCTick } type RejectedMedicalConditionStore = { RejectedMedicalCondition: MedicalCondition LastMod: UTCTick }
需要编写accumulateConditions方法,满足以下要求:
- 参数
medicalConditionsList既支持传入MedicalConditionStore list类型,也支持传入RejectedMedicalConditionStore list类型 - 不允许额外为两种类型定义外层可区分联合类型,避免频繁的包装、解包模式匹配操作
已尝试两种无效写法:
- 泛型成员约束写法,编译不通过:
let accumulateConditions (medicalConditionsList: ^a list when ^a :(member F: MedicalCondition)) = ...
- 匿名联合类型写法,语法不支持:
let accumulateConditions (medicalConditionsList: RejectedMedicalConditionStore list | MedicalConditionStore list) = ...
实现方案
方案1:静态解析类型参数(SRTP)+ 类型扩展(零侵入、编译期检查)
这是F#中实现临时多态最轻量化的方案,不需要修改原有类型定义,无运行时开销:
// 主函数必须标记为inline以启用静态类型约束 let inline accumulateConditions medicalConditionsList = medicalConditionsList |> List.map (fun item -> // 静态约束调用统一的条件提取方法 (^a : (member get_Condition : unit -> MedicalCondition) item) ) // 此处补充自定义的累积逻辑,如去重、聚合、状态合并等 |> id // 通过类型扩展为两种现有记录补充统一的提取方法,不需要修改原有类型代码 type MedicalConditionStore with member inline this.get_Condition() = this.MedicalCondition type RejectedMedicalConditionStore with member inline this.get_Condition() = this.RejectedMedicalCondition
方案特性:
- 所有类型检查在编译期完成,无运行时装箱、类型转换开销
- 零侵入,不需要修改原有记录的定义代码,也不需要新增包装类型
- 调用时无需手动做类型判断、模式匹配,直接传入对应类型列表即可:
// 传入普通病症存储列表 let normalStoreList: MedicalConditionStore list = [] let res1 = accumulateConditions normalStoreList // 传入被驳回的病症存储列表 let rejectedStoreList: RejectedMedicalConditionStore list = [] let res2 = accumulateConditions rejectedStoreList - 后续新增同结构存储类型时,仅需为新类型补充同名的
get_Condition扩展方法,主函数不需要任何修改即可兼容新类型。
方案2:接口 + 类型扩展(非内联、适合跨程序集场景)
如果不希望使用内联函数,或者需要将函数作为一等值跨程序集传递,可以通过定义统一接口+类型扩展实现接口的方式实现,同样满足零侵入要求:
// 定义统一的提取接口 type IHasMedicalCondition = abstract member GetCondition : unit -> MedicalCondition // 通过类型扩展让两种现有记录实现接口,不需要修改原有类型定义 type MedicalConditionStore with interface IHasMedicalCondition with member this.GetCondition() = this.MedicalCondition type RejectedMedicalConditionStore with interface IHasMedicalCondition with member this.GetCondition() = this.RejectedMedicalCondition // 普通泛型函数即可,不需要标记inline let accumulateConditions (medicalConditionsList: #IHasMedicalCondition list) = medicalConditionsList |> List.map (fun item -> item.GetCondition()) // 此处补充自定义的累积逻辑 |> id
两种错误写法的问题原因
- 第一种成员约束写法问题:
- 以
^开头的静态解析类型参数必须配合inline函数使用,普通函数无法识别这类编译期约束 - 约束语法
member F: MedicalCondition不符合F#规范,成员约束需要明确指定完整成员签名;且两种记录的存储字段名并不一致(一个为MedicalCondition,一个为RejectedMedicalCondition),无法直接通过统一属性名匹配约束
- 以
- 第二种联合类型写法问题:
- F#不支持匿名类型联合语法,即
T1 | T2的类型标注方式不合法,所有联合类型必须显式定义后才能使用,不符合不新增包装联合类型的要求。
- F#不支持匿名类型联合语法,即
内容的提问来源于stack exchange,提问作者Oanta William Richard
相关产品推荐
相关产品推荐

