2012-07-19 10 views
5

Quiero añadir la impresión de depuración en mi proyecto con una función que tiene un tipo de firma algo como:¿Cómo gestionar la impresión de depuración en F #

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

es decir, debe tener un bool que indica si estamos o no en modo prolijo modo, y usar eso para tomar la decisión sobre si imprimir o no.

Por ejemplo, digamos dprint : bool -> Printf.TextWriterFormat<'a> -> 'a entonces me gustaría este comportamiento:

> dprint true "Hello I'm %d" 52;; 
Hello I'm 52 
val it : unit =() 
> dprint false "Hello I'm %d" 52;; 
val it : unit =() 

La idea es que una marca de línea de comandos se puede utilizar para evitar el control de esta salida. También quiero evitar un costo de tiempo de ejecución en el caso "no detallado". Es posible definir una función que funciona de esta manera usando 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 

pero la impresión/ignorando una secuencia de números con List.iter (dprint b "%A") [1..10000] (b \ in {verdadero, falso}) realiza cantidad 1.5s para ambos valores de b en mi máquina.

me ocurrió con otro método utilizando la reflexión que construye una función apropiada escrito para descartar los argumentos de formato:

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

pero aquí la reflexión parece demasiado caro (incluso más que la que se hace dentro de las bibliotecas estándar complicada definición de printf a veces).

no quiero ensuciar mi código con cosas como:

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

o cierres:

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

así, ¿hay alguna otra solución?

Respuesta

5

me gusta su solución mediante la reflexión. ¿Qué hay de caché en el nivel de tipo para que pagues el precio de la reflexión solo una vez por tipo? Por ejemplo:

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 

un pragmático simplemente usaría el formato C# maquinaria para la impresión rápida en lugar de esto. Evito las funciones Printf en el código de producción debido a la sobrecarga que tienen, como usted señala. Pero la impresión F # definitivamente se siente más agradable de usar.

Mis #time resultados para List.iter (dprint false "%A") [1..10000]:

  • versión original: 0,85
  • versión original con la reflexión: 0,27
  • La versión propuesta: 0.03
+0

Esta es una sugerencia muy interesante, gracias. Sin embargo, parece que no se almacena en caché como se esperaba, es lento en el ejemplo de 'List.iter' en mi pregunta. Traté de agregar un diccionario a 'Format' para caché explícitamente las funciones, pero parece que tampoco funciona. Quizás 'unbox' o' typeof' es caro. – rneatherway

+0

@robin, ahora veo un problema con 'mkKn', permítanme editar .. – t0yv0

+0

@robin - Creo que con la corrección anterior, la reflexión solo debe usarse en la primera invocación (por tipo). Sin embargo, todavía existe el costo de invocar cierres al curry no optimizados de la forma '(diversión x y z ->()) x y z', incluido el boxeo introducido por' MakeFunction' .. – t0yv0

1

Por qué no usar #defines Eso sí,

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

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

En mi máquina de la prueba de la muestra toma 0.002s en la versión optimizada

+0

Desafortunadamente, ese enfoque solo permite pasar un único argumento. Si ve mi enfoque basado en la reflexión, construye una función curried que acepta un número arbitrario de parámetros. Entonces, su dprint funciona bien para 'dprint"% d "1' pero no' dprint "% d,% d" 1 2'. – rneatherway

+0

Esto tampoco permite las pruebas en tiempo de ejecución para el indicador de depuración. – t0yv0

+0

De acuerdo toyvo, esta no es mi solución preferida. – rneatherway

2

¿Qué tal esto:

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

entonces usted puede escribir:

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

Debug.WriteLine(...) está marcado con [<Conditional("DEBUG")>] por lo que el F # compilador debe ser capaz de eliminar toda la declaración en Tiempo de compilación (aunque tendrá que experimentar y comprobar el IL compilado para ver si realmente lo hace.

Tenga en cuenta que esta solución solo funciona si no le importa cambiar la verbosidad en tiempo de ejecución. Si ese es el caso, tendrás que buscar una solución diferente.

ACTUALIZACIÓN: Por curiosidad, acabo de probar este código (funciona) y el compilador F # 2.0 no compila todo (incluso con optimizaciones), por lo que la velocidad es la misma ya sea depuración o no. Puede haber otras formas de hacer que el compilador elimine toda la declaración para solucionar el problema de velocidad, pero solo tendrá que experimentar un poco para averiguarlo.

+0

¿La "declaración completa" incluye el ksprintf? Porque esta función hace el formateo costoso. Gracias por la sugerencia. Sin embargo, estoy interesado en cambiar la verbosidad en tiempo de ejecución. – rneatherway

+0

Gracias por la actualización Jack. – rneatherway

+2

_ "para que el compilador F # pueda eliminar toda la instrucción en tiempo de compilación" _ >> no del todo. El atributo se agrega, al igual que C#, y el método permanece, para que pueda acceder al método (tipo de seguridad, etc.) en las compilaciones de depuración y liberación. El JIT luego eliminará el método y los sitios de llamadas durante la compilación de JIT. Por lo general, es mejor marcar todo el método; de lo contrario, se seguirán llamando a otras partes del método. F # _hace forzar unit_: un método marcado con 'ConditionalAttribute' debe devolver la unidad. – Abel

Cuestiones relacionadas