F#实现可扩展异构状态函数列表,选继承/可区分联合/泛型?
解决方案
你可以通过接口+泛型封装的方式模拟存在类型,既保证全程静态类型安全,又支持用户自定义扩展状态与函数,不需要用到可区分联合或类继承。
核心思路是把「步进函数 + 对应状态」打包成统一接口类型的实例,隐藏内部具体的状态类型,所有实例可以放在同一列表中统一处理。
完整实现代码
Lib.fs
module Lib type FState = {y:int; z:string} let f (x:float) (s:FState) : float * FState = (x+1.0, {s with y=s.y+1}) type GState = {y:string} let g (x:float) (s:GState) : float * GState = (x*2.0, s) type HState = {z:float} let h (s:HState) : float * HState = (0.0, s) // 统一的计算接口,隐藏内部状态类型 type ILineComputation = /// 执行一次计算,返回结果值和更新后的计算实例 abstract member Run : unit -> float * ILineComputation // 泛型私有实现类,封装具体状态和步进函数 type private LineComputation<'State>(state: 'State, step: 'State -> float * 'State) = interface ILineComputation with member _.Run() = let value, newState = step state value, LineComputation<'State>(newState, step) :> ILineComputation // 辅助创建函数,自动推导状态类型 let createComputation step initialState = LineComputation<'State>(initialState, step) :> ILineComputation // 批量执行所有计算 let steps (computations: ILineComputation list) = computations |> List.map (fun c -> c.Run())
Program.fs
open Lib type EState = {y:float} let e (s:EState) : float * EState = (1.0, {s with y=s.y/2.0}) [<EntryPoint>] let main argv = // 将每个步进函数和对应初始状态打包为统一接口实例 let computations = [ createComputation (f 0.0) {FState.y=1; z=""} createComputation (f 1.0) {FState.y=1; z=""} createComputation (g 5.0) {GState.y=""} createComputation h {HState.z=2.0} createComputation e {EState.y=4.0} ] // 执行批量计算,得到所有结果和更新后的计算实例 // 如需计算下一轮坐标,直接将返回的ILineComputation列表传入steps即可 let results: (float * ILineComputation) list = steps computations 0 // return an integer exit code
方案说明
- 静态类型安全:
createComputation会在编译期检查步进函数和状态的类型是否匹配,类型错误会直接在编译阶段抛出,没有运行时转型风险。 - 可扩展:用户自定义新的状态类型和步进函数时,不需要修改Lib模块的任何代码,直接用
createComputation打包即可加入列表执行。 - 适配图表场景:每次调用
steps会返回所有线条的下一个坐标值,以及更新了内部状态的计算实例,下一轮计算可以直接复用返回的实例,符合逐帧绘制的需求。
内容的提问来源于stack exchange,提问作者symbiont
相关产品推荐
相关产品推荐

