F# 计算表达式中类似 Printf 的函数
Printf-like function in F# computation expression
我正在尝试用 F# 编写代码,允许记录到自定义源并使用生成随机数的可交换实现。
我尝试将这些函数作为计算表达式的上下文传递,而不是将日志记录 function/random 生成器传递给应用程序中的每个函数。
我注意到声明 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 传递格式参数的能力
编辑
绑定实现有错误,应该是:
member this.Bind (x, f) = let (Simulation(simulation)) = x in Simulation (fun simulator -> Simulation.run simulator (f (simulation simulator)))
这里的问题是你的 log
函数 returns 包装在 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"
returns 是一个函数 int -> Simulation<unit>
而不是像以前那样 Simulation<int -> unit>
。
但是,这也会使 log
函数单态化:编译器会看到它与单个 int
参数一起使用,并将其类型固定为接受 int
s,然后如果你尝试不同的东西:
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 ->
或 string -> bool ->
或您的格式字符串指定的任何其他内容。
有了这个,任何格式都可以编译:
do! Simulation.log "Value: %d" x
do! Simulation.log "Value: %s" "foo"
do! Simulation.log "I have %d apples which are %s" 42 "rotten"
我正在尝试用 F# 编写代码,允许记录到自定义源并使用生成随机数的可交换实现。
我尝试将这些函数作为计算表达式的上下文传递,而不是将日志记录 function/random 生成器传递给应用程序中的每个函数。
我注意到声明 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 传递格式参数的能力
编辑
绑定实现有错误,应该是:
member this.Bind (x, f) = let (Simulation(simulation)) = x in Simulation (fun simulator -> Simulation.run simulator (f (simulation simulator)))
这里的问题是你的 log
函数 returns 包装在 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"
returns 是一个函数 int -> Simulation<unit>
而不是像以前那样 Simulation<int -> unit>
。
但是,这也会使 log
函数单态化:编译器会看到它与单个 int
参数一起使用,并将其类型固定为接受 int
s,然后如果你尝试不同的东西:
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 ->
或 string -> bool ->
或您的格式字符串指定的任何其他内容。
有了这个,任何格式都可以编译:
do! Simulation.log "Value: %d" x
do! Simulation.log "Value: %s" "foo"
do! Simulation.log "I have %d apples which are %s" 42 "rotten"