2012-07-19 34 views
5

我要調試打印添加到我的項目與具有類型簽名類似的功能:如何管理在F#調試印刷

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中\ {TRUE,FALSE})開量爲1.5秒B的兩個值在我的機器上。

我想出了另一種方法,使用的反映,建立適當類型的函數放棄格式化參數:

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) 

所以,有沒有其他解決辦法?

回答

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
+0

這是一個非常有趣的建議,謝謝。它似乎並不像預期的那樣緩存 - 它在我的問題中的List.iter示例中很慢。我嘗試添加一個字典到'Format'來顯式緩存這些函數,但是這似乎也不起作用。也許'unbox'或'typeof'是昂貴的。 – rneatherway 2012-07-19 13:47:12

+0

@robin,我現在看到'mkKn'的問題,讓我編輯.. – t0yv0 2012-07-19 14:03:14

+0

@robin - 我認爲與上述修正應該只用於第一次調用(每種類型)。然而,仍然存在調用形式爲'(fun x y z - >())x y z'的未優化curry關閉的代價,包括'MakeFunction'引入的裝箱效果。 – t0yv0 2012-07-19 14:06:27

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.002s的優化版本

+0

不幸的是,該方法只允許傳入一個參數。如果您看到我的基於反射的方法,它會構造一個curried函數,接受任意數量的參數。所以,你的dprint對'dprint「%d」1'正常工作,但不能'dprint「%d,%d」1 2'。 – rneatherway 2012-07-19 12:52:35

+0

這也不允許在運行時測試調試標誌。 – t0yv0 2012-07-19 13:12:36

+0

同意toyvo,這不是我的首選解決方案。 – rneatherway 2012-07-19 13:50:07

2

如何:

/// 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編譯器沒有編譯所有東西(即使進行了優化),所以無論調試與否,速度都是一樣的。可能有其他方法讓編譯器消除整個語句來解決速度問題,但是您只需稍微嘗試一下就可以找出答案。

+0

「整個陳述」是否包含ksprintf?因爲這個功能花費昂貴的格式。感謝您的建議。儘管如此,我很想改變運行時的詳細程度。 – rneatherway 2012-07-19 13:41:30

+0

感謝您的更新傑克。 – rneatherway 2012-07-19 13:49:42

+2

_「因此F#編譯器應該能夠在編譯時消除整個語句」_ >>不完全。該屬性被添加,就像C#一樣,並且該方法保持不變,以便您可以在Debug和Release版本中訪問該方法(輸入safety等)。 JIT將在JIT編譯期間刪除該方法和調用站點。標記整個方法通常會更好,否則該方法的其他部分仍將被調用。 F#_強制執行unit_:標有'ConditionalAttribute'的方法必須返回單位。 – Abel 2015-11-19 05:56:25