7

次のような型シグネチャを持つ関数を使用して、プロジェクトにデバッグ印刷を追加したいと考えています。

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

つまり、冗長モードかどうかを示す 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 = ()

これは、コマンド ライン フラグを使用して、この出力の制御を回避できるという考え方です。また、「冗長ではない」場合のランタイム コストも避けたいと考えています。を使用して、このように機能する関数を定義することができます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

しかし、(b \in {true,false}) を使用して一連の数字を印刷/無視するとList.iter (dprint b "%A") [1..10000]、私のマシンでは 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

これはどう:

/// 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 に答える
5

リフレクションを使用したソリューションが気に入っています。リフレクションの代償を型ごとに 1 回だけ支払うように、型レベルでキャッシュするのはどうですか? 例えば:

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 に答える
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 に答える