F#计算表达式中带printf风格参数的日志函数实现问题
解决F#计算表达式中日志的printf风格简洁调用问题
嘿,你的这个用计算表达式传递日志和随机数生成器上下文的思路特别好,完美避免了在每个函数里到处传依赖的麻烦!现在咱们来搞定日志调用的简洁性问题,让它能像你期望的那样直接用do! Simulation.log "Value: %d" x这种方式调用,同时保留printf的格式化能力。
问题根源分析
你当前的Simulation.log返回的是一个Simulation<(string -> unit)>,也就是需要先通过let!把日志函数从上下文中取出来,再调用它。咱们需要把它改成直接接受格式化参数并执行日志操作的函数,返回Simulation<unit>,这样就能用do!直接执行了。
解决方案代码
下面是修改后的完整代码,关键改动在Simulation.log的实现:
module Test type Simulator = { random : int * int -> int; logger : string -> unit } module Simulator = let create = let random = new System.Random() { random = fun (min, max) -> random.Next(min, max) logger = fun str -> printfn "%s" str } type Simulation<'T> = Simulation of (Simulator -> 'T) module Simulation = /// Runs a simulation given a simulator let inline run state simulation = let (Simulation(play)) = simulation in play state /// Returns a random number let random min max = Simulation (fun simulator -> simulator.random (min, max)) /// 新的log函数:支持printf风格格式化,直接返回Simulation<unit> let log fmt = Simulation (fun simulator -> // 用ksprintf把格式化后的字符串传给simulator的logger Printf.ksprintf simulator.logger fmt) type SimulationBuilder() = member this.Bind (x, f) = let (Simulation(simulation)) = x Simulation (fun simulator -> Simulation.run simulator (f (simulation simulator))) member this.Return (x) = Simulation (fun _ -> x) // 新增ReturnUnit,让do!调用更顺畅(可选,但推荐) member this.ReturnUnit () = Simulation (fun _ -> ()) let simulate = new SimulationBuilder() let simpleSimulation = simulate { let! x = Simulation.random 2 12 // 现在可以直接这样写了!简洁又符合习惯 do! Simulation.log "Value: %d" x // 还支持更复杂的格式化,比如多个参数 do! Simulation.log "Random value between %d and %d is %d" 2 12 x return x; } Simulation.run Simulator.create simpleSimulation |> ignore
关键改动说明
重新设计
log函数:- 把
log改成了一个泛型函数,利用F#的Printf.ksprintf来处理格式化逻辑。ksprintf会把格式化后的字符串传递给我们指定的函数(这里就是simulator.logger)。 - 现在
log接受任意printf风格的格式化字符串和参数,返回的是Simulation<unit>,刚好可以用do!在计算表达式里直接执行。
- 把
可选优化:新增
ReturnUnit成员:- 在
SimulationBuilder里添加ReturnUnit成员,是为了让do!调用返回unit的Simulation时更顺畅,不过即使不加,现有代码也能正常运行,加了之后语义更清晰。
- 在
测试效果
运行这段代码,你会看到控制台输出对应的日志信息,完全符合预期,而且调用方式简洁很多,再也不用先绑定logger再调用啦!
内容的提问来源于stack exchange,提问作者ghord
相关产品推荐
相关产品推荐

