我想使用具有类型签名的函数向我的项目添加调试打印:
bool -> Printf.TextWriterFormat<'a> -> 'a
Run Code Online (Sandbox Code Playgroud)
即它应该采取一个bool来表明我们是否处于详细模式,并使用它来决定是否打印.
例如,让我们说dprint : bool -> Printf.TextWriterFormat<'a> -> 'a我希望这种行为:
> dprint true "Hello I'm %d" 52;;
Hello I'm 52
val it : unit = ()
> dprint false "Hello I'm %d" 52;;
val it : unit = ()
Run Code Online (Sandbox Code Playgroud)
我们的想法是可以使用命令行标志来避免控制此输出.我还想避免"非冗长"情况下的运行时成本.可以使用以下方法定义一个这样工作的函数kprintf:
let dprint (v: bool) (fmt: Printf.StringFormat<'a,unit>) =
let printVerbose (s: string) =
if v then System.Console.WriteLine(s)
fmt |> Printf.kprintf printVerbose
Run Code Online (Sandbox Code Playgroud)
但打印/忽略一系列数字List.iter (dprint b "%A") [1..10000](b\in {true,false})对我机器上的两个b值都需要1.5s.
我想出了另一种使用反射的方法,该方法构建了一个适当类型的函数来丢弃格式化参数:
let dprint (v: bool) (fmt: Printf.TextWriterFormat<'a>) : 'a =
let rec mkKn (ty: System.Type) =
if FSharpType.IsFunction(ty) then
let _, ran = FSharpType.GetFunctionElements(ty)
FSharpValue.MakeFunction(ty,(fun _ -> mkKn ran))
else
box ()
if v then
printfn fmt
else
unbox<'a> (mkKn typeof<'a>)
Run Code Online (Sandbox Code Playgroud)
但是这里的反射似乎太昂贵了(甚至比标准库中的复杂定义printf有时更多).
我不想用以下内容丢弃我的代码:
if !Options.verbose then
printfn "Debug important value: %A" bigObject5
Run Code Online (Sandbox Code Playgroud)
或关闭:
dprint (fun () -> printfn "Debug important value: %A" bigObject5)
Run Code Online (Sandbox Code Playgroud)
那么,还有其他解决方案吗?
我喜欢使用反射的解决方案.如何在类型级别缓存它,以便每种类型只支付一次反射价格?例如:
let rec mkKn (ty: System.Type) =
if Reflection.FSharpType.IsFunction(ty) then
let _, ran = Reflection.FSharpType.GetFunctionElements(ty)
// NOTICE: do not delay `mkKn` invocation until runtime
let f = mkKn ran
Reflection.FSharpValue.MakeFunction(ty, fun _ -> f)
else
box ()
[<Sealed>]
type Format<'T> private () =
static let instance : 'T =
unbox (mkKn typeof<'T>)
static member Instance = instance
let inline dprint verbose args =
if verbose then
printfn args
else
Format<_>.Instance
Run Code Online (Sandbox Code Playgroud)
实用主义者只会使用快速的C#格式化打印机器而不是这个.Printf正如你所指出的,我避免了生产代码中的函数,因为它们有开销.但是F#打印肯定会让用起来更好.
我的#time结果是List.iter (dprint false "%A") [1..10000]:
这个怎么样:
/// Prints a formatted string to DebugListeners.
let inline dprintfn fmt =
Printf.ksprintf System.Diagnostics.Debug.WriteLine fmt
Run Code Online (Sandbox Code Playgroud)
然后你可以写:
dprintfn "%s %s" "Hello" "World!"
Run Code Online (Sandbox Code Playgroud)
Debug.WriteLine(...)被标记为[<Conditional("DEBUG")>],因此 F# 编译器应该能够在编译时消除整个语句(尽管您必须进行实验并检查编译后的 IL 以查看它是否确实如此。
请注意,只有当您不关心在运行时更改详细程度时,此解决方案才有效。如果是这种情况,您将不得不寻找不同的解决方案。
更新:出于好奇,我只是尝试了这段代码(它确实有效),并且 F# 2.0 编译器不会编译所有内容(即使启用了优化),因此无论调试与否,速度都是相同的。可能还有其他方法可以让编译器消除整个语句来解决速度问题,但您只需进行一些实验即可找到答案。