7

我想使用具有类型签名的函数向我的项目添加调试打印,例如:

bool -> Printf.TextWriterFormat<'a> -> 'a

即它应该接受一个布尔值来指示我们是否处于详细模式,并使用它来决定是否打印。

例如,假设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 = ()

这个想法是可以使用命令行标志来避免控制此输出。我还想避免“不冗长”情况下的运行时成本。可以使用以下方法定义一个像这样工作的函数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

但是在我的机器上打印/忽略带有List.iter (dprint b "%A") [1..10000](b \in {true,false}) 的数字序列对于 b 的两个值都需要 1.5 秒。

我想出了另一种使用反射的方法,它构建了一个适当类型的函数来丢弃格式化参数:

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>)

但是这里的反射似乎太昂贵了(甚至比标准库中的复杂定义printf有时还要昂贵)。

我不想在我的代码中乱扔以下东西:

if !Options.verbose then
    printfn "Debug important value: %A" bigObject5

或关闭:

dprint (fun () -> printfn "Debug important value: %A" bigObject5)

那么,还有其他解决方案吗?

4

3 回答 3

5

我喜欢您使用反射的解决方案。如何在类型级别缓存它,以便您为每种类型只支付一次反射的代价?例如:

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

实用主义者只会使用快速 C# 格式的打印机器来代替它。Printf正如您所指出的,我避免在生产代码中使用函数,因为它们具有开销。但是 F# 打印肯定感觉更好用。

我的#time结果List.iter (dprint false "%A") [1..10000]

  • 原始版本:0.85
  • 带反射的原始版本:0.27
  • 建议版本:0.03
于 2012-07-19T13:05:53.563 回答
5

这个怎么样:

/// Prints a formatted string to DebugListeners.
let inline dprintfn fmt =
    Printf.ksprintf System.Diagnostics.Debug.WriteLine fmt

然后你可以写:

dprintfn "%s %s" "Hello" "World!"

Debug.WriteLine(...)标记为,[<Conditional("DEBUG")>]因此 F# 编译器应该能够在编译时消除整个语句(尽管您必须试验并检查编译后的 IL 以查看它是否确实如此。

请注意,此解决方案仅在您不关心在运行时更改详细程度时才有效。如果是这种情况,您将不得不寻找不同的解决方案。

更新:出于好奇,我只是尝试了这段代码(它确实有效)并且 F# 2.0 编译器不会编译所有内容(即使进行了优化),所以无论是否调试速度都是一样的。可能还有其他方法可以让编译器消除整个语句以解决速度问题,但您只需进行一些试验即可找到答案。

于 2012-07-19T13:33:36.007 回答
1

为什么不使用#defines只是做

let dprint  (fmt: Printf.StringFormat<'a,unit>) =  
#if DEBUG
  let printVerbose (s: string) =
        System.Console.WriteLine(s)

  fmt |> Printf.kprintf printVerbose
#else
   fun _ -> ()

在我的机器上,优化版本的样本测试需要 0.002 秒

于 2012-07-19T11:18:26.500 回答