2011-09-20 12 views
5

Poco a poco voy cambiando a F # para muchos de mis proyectos en el hogar, pero estoy un tanto perplejo en cuanto a cómo unir aplicaciones completas y, más particularmente, cuestiones transversales.F # registros/repositorios de la estructura de la aplicación, etc.

En C# si quiero registrar cosas, utilizaría la inyección de dependencia para pasar un ILogger a cada clase, y esto se puede llamar agradable y fácil desde el código. Puedo verificar en mis pruebas que, dada una situación particular, los registros se escriben, pasando un simulacro y verificándolo.

public class MyClass 
{ 
    readonly ILogger _logger; 
    public MyClass(ILogger logger) 
    { 
     _logger = logger; 
    } 

    public int Divide(int x, int y) 
    { 
     if(y == 0) 
     { 
      _logger.Warn("y was 0"); 
      return 0; 
     } 
     return x/y; 
    } 
} 

En F # estoy usando módulos mucho más, lo que lo anterior se convertiría en

module Stuff 

let divde x y = 
    match y with 
    | 0 -> 0 
    | _ -> x/y 

Ahora si tuviera un módulo llamado registro tan sólo pudiera abrir eso y utilizar una función de registro de allí en el caso de que y sea 0, ¿pero cómo inyectaría esto para la prueba unitaria?

Podría hacer que cada función tomara una función de registro (cadena -> unidad) y luego utilizara una aplicación parcial para conectarlas, pero eso parece una gran cantidad de trabajo, como crear una nueva función que envuelva la llamada real dentro una llamada de registro. ¿Hay un patrón en particular o un poco de F # que me falta que pueda hacerlo? (He visto la función kprintf pero todavía no sé cómo se especificaría la función para varios escenarios de prueba, mientras se usa una implementación concreta para la aplicación completa)

Del mismo modo, ¿cómo cerraría un repositorio? que obtuvo datos? ¿Necesita tener una clase instanciada y establecer las funciones CRUD en él, o hay una forma de inyectar qué módulos se abren (aparte de #define)

+0

http://stackoverflow.com/questions/2003487/architectural-thinking-in-functional-languages ​​http://stackoverflow.com/questions/192090/how-do-you-design-a-functional-program –

Respuesta

2

Esta es una respuesta básica. En primer lugar, parece que estás pensando en clases y módulos como intercambiables. Las clases encapsulan datos, y en ese sentido son más análogos a los registros y DU. Los módulos, por otro lado, encapsulan la funcionalidad (están compilados en clases estáticas). Por lo tanto, creo que ya mencionó sus opciones: aplicación de función parcial, pasando las funciones como datos, o ... inyección de dependencia. Para su caso particular, parece más fácil mantener lo que tiene.

Una alternativa es usar las directivas de preprocesador para incluir diferentes módulos.

#if TESTING 
open LogA 
#else 
open LogB 
#endif 

DI no es necesariamente un mal ajuste en un lenguaje funcional. Vale la pena señalar que F # hace que las interfaces de definición y cumplimiento sean incluso más fáciles que, por ejemplo, C#.

+0

hmmm, así que imagina que F # no tenía clases solo registros y DU, entonces el registro tendría que ir en módulos ya que normalmente desea iniciar sesión sobre una operación (al menos en la depuración) en lugar del resultado de una operación. Esto significaría que DI está fuera de la ventana, por lo que la aplicación parcial es todo lo que queda. ¿Puede recomendar alguna lectura sobre arquitectura funcional por casualidad? Como ha sido por tanto tiempo, estoy seguro de que este tipo de cosas ya se han resuelto, pero todos los libros que he visto se concentran en el "pequeño" en lugar del "gran" – Dylan

+0

. Desearía haberlo hecho. Recuerdo haber luchado con esto temprano también. Esperaba que la arquitectura "grande" fuera radicalmente diferente en F #. Algunos lenguajes funcionales permiten parametrizar módulos. Desafortunadamente, F # no. OO sigue siendo un buen enfoque "en general". Sé que la pregunta ha sido planteada algunas veces en SO. Apareció una búsqueda rápida: http://stackoverflow.com/questions/2003487/architectural-thinking-in-functional-languages. Puede haber otros. – Daniel

+0

Gracias por ese enlace, hice una búsqueda anterior pero no pude encontrar nada que realmente me diera alguna orientación. Sin embargo, creo que tienes razón con la aplicación parcial, supongo que solo tiene que subir un nivel de encapsulado antes de que las dependencias comiencen a conectarse por la aplicación de alojamiento. – Dylan

3

Si no necesita cambiar el registrador en tiempo de ejecución, entonces usar una directiva de compilación o #if para elegir entre dos implementaciones de un registrador (como lo sugirió Daniel) es probablemente la mejor y la más simple forma de hacerlo.

Desde el punto de vista funcional, la inyección de dependencia significa lo mismo que la parametrización de todos los códigos mediante una función de registro. Lo malo es que necesitas propagar la función a todas partes (y eso hace que el código sea un poco desordenado). También puede simplemente hacer una variable mutable global en un módulo y configurarla en una instancia de alguna interfaz ILogger - Creo que esta es una solución bastante aceptable para F #, porque necesita cambiar esta variable solo en un par de lugares.

Otra alternativa (más "pura") sería definir un flujo de trabajo (también conocido como mónada) para el registro. Esta es una buena opción solo si escribe todo su código en F #. Este ejemplo se analiza en el Capítulo 12 de mi libro, que es available as a free sample.A continuación, puede escribir algo como esto:

let write(s) = log { 
    do! logMessage("writing: " + s) 
    Console.Write(s) } 

let read() = log { 
    do! logMessage("reading") 
    return Console.ReadLine() } 

let testIt() = log { 
    do! logMessage("starting") 
    do! write("Enter name: ") 
    let! name = read() 
    return "Hello " + name + "!" } 

Lo bueno de este enfoque es que es una buena técnica funcional. El código de prueba debería ser fácil, ya que una función que devuelve Log<'TResult> esencialmente le da un valor así como un registro de sus efectos secundarios, por lo que puede simplemente comparar los resultados. Sin embargo, puede ser una exageración, porque tendrías que ajustar todos los cálculos que usan el registro en el bloque log { .. }.

+0

Gracias Tomas, Todavía estoy empeñado en que la mutabilidad es malvada. En mi código C# siempre hago dependencias de solo lectura. Si hago un registrador mutable en el código, entonces supongo que no hay una manera fácil de protegerlo de una reasignación accidental. Supongo, como mencioné en otro comentario, que los niveles superiores de la aplicación (funcionalidad derivada) no accederían a los módulos inferiores de todos modos. – Dylan

+0

@Dylan - Sí, la declaración de módulos es sensible a los pedidos, por lo que su módulo de registro debería ir primero. Es cierto que la mutabilidad es malvada (en F #) en general. Sin embargo, si no necesita volver a asignar el registrador, entonces es la opción más simple. –

0

Aquí es una implementación de su código C# en C#, que es similar a la inicial C#

module stuff 

type MyClass(logger:#Ilogger) = 
    member x.Divide a b = 
     if b=0 then 
      logger.Warn("y was 0") 
      0 
     else 
      x/y 

esto debería permitir el uso de las mismas técnicas que conoces de C# para su registro

+0

gracias, pero prefiero usar C# para este tipo de enfoque. Realmente no lo dejé claro pero era más por cómo harías este enfoque usando solo registros y definiciones de funciones sin clases – Dylan

+0

No menor - no necesitas el tipo '# ILogger' en este caso, porque F # inserta el molde automáticamente al llamar a un método o un constructor. Usar el tipo de hash aquí parece un poco raro, supongo que incluso hace que toda la clase sea genérica (parametrizada sobre el tipo de registrador), que no es necesaria aquí. –

+0

@Tomas - eso es lo que sucede cuando no me molesto en usar el compilador para verificar mi código –

Cuestiones relacionadas