【问题标题】:Printf-like function in F# computation expressionF# 计算表达式中的类似 Printf 的函数
【发布时间】:2020-06-19 15:43:13
【问题描述】:

我正在尝试在 F# 中编写代码,以允许登录到自定义源并使用生成随机数和可交换实现。

我没有将日志记录函数/随机生成器传递给应用程序中的每个函数,而是尝试将这些函数作为计算表达式的上下文传递。

我注意到它与 state monad 实现非常相似,所以我尝试编写类似的东西。

我的概念证明工作正常,生成数字的工作非常好,但我无法使其与我的日志记录函数的 printf 样式参数很好地工作:

module Test

type Simulator = { random : int * int -> int; logger : string -> unit }

module Simulator =
    let create = let random = new System.Random() in 
                     { 
                     random = fun (min, max) -> random.Next(min, max)
                     logger = fun str -> (printfn "%s" str |> ignore) 
                     }

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))
    /// Writes to simulation log
    let log = Simulation (fun simulation -> Printf.ksprintf simulation.logger)


type SimulationBuilder() =
    member this.Bind (x, f) = let (Simulation(simulation)) = x in Simulation (fun simulator -> f (simulation simulator))
    member this.Return (x) = x

let simulate = new SimulationBuilder()

let simpleSimulation =
    simulate
        {
        //very nice, working
        let! x = Simulation.random 2 12
        //this is working, but verbose
        let! logger = Simulation.log 
        do logger "Value: %d" x
        //I want to write 
        //do! Simulation.log "Value: %d" x
        // or something similar
        return x;
        }

Simulation.run Simulator.create simpleSimulation |> ignore

有人可以帮助我吗?我对编写自定义计算表达式很陌生。

编辑

注意日志函数可以有签名

let log str = Simulation (fun simulation -> simulation.logger str)

而且很容易调用:

simulate
   {
   ...
   do! Simulation.log "Hello world!"
   ...
   }

但是在这里我失去了在不使用 sprintf 的情况下传递格式参数的能力

编辑

bind实现有错误,应该是:

member this.Bind (x, f) = let (Simulation(simulation)) = x in Simulation (fun simulator -> Simulation.run simulator (f (simulation simulator)))

【问题讨论】:

    标签: f# monads computation-expression


    【解决方案1】:

    这里的问题是你的log函数返回的是包裹在Simulation中的打印函数,所以你必须使用一个额外的let!来解开它,然后才能使用它。

    但没有什么能阻止您将 函数的结果 包装在 Simulation 中,而不是函数本身:

    let log = Printf.ksprintf (fun str -> Simulation (fun simulation -> simulation.logger str))
    

    这将使您的代码编译:

    do! Simulation.log "Value: %d" x
    

    因为现在表达式Simulation.log "Value: %d" 返回一个函数int -&gt; Simulation&lt;unit&gt;,而不是像以前那样返回Simulation&lt;int -&gt; unit&gt;


    但是,这也会使log 函数单态化:编译器将看到它与单个int 参数一起使用,并将其类型固定为接受ints,然后如果您尝试不同的方法:

    do! Simulation.log "Value: %s" "foo"
    

    它不会再次编译,抱怨它期待int,但给出了string

    要解决这个新问题,您必须通过提供显式泛型类型注释并准确指定应如何将其转换为 ksprintf 来帮助编译器:

    let log (format: Printf.StringFormat<'T, _>) = 
      Printf.ksprintf (fun str -> Simulation (fun simulation -> simulation.logger str)) format
    

    这里,'T 表示参数字符串,例如 int -&gt;string -&gt; bool -&gt; 或您的格式字符串指定的任何其他内容。

    有了这个,任何格式都可以编译:

    do! Simulation.log "Value: %d" x
    do! Simulation.log "Value: %s" "foo"
    do! Simulation.log "I have %d apples which are %s" 42 "rotten"
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多